网站怎么看是谁做的,要找做冲压件的厂去哪个网站找,温州网站建设公司公司哪家好,关于外贸公司的网站4.1 进程四要素什么是进程#xff1f;1#xff1a;有一段代码段供其执行#xff0c;这代码段不一定是进程所专用#xff0c;可以与其他进程公用。2#xff1a;每个进程有其专用的系统空间的堆栈(栈)【这个栈是进程起码的“私有财产”】3#xff1a;在内核中#xff0c;要…4.1 进程四要素什么是进程1有一段代码段供其执行这代码段不一定是进程所专用可以与其他进程公用。2每个进程有其专用的系统空间的堆栈(栈)【这个栈是进程起码的“私有财产”】3在内核中要有task_struct 进程控制块【task_struct进程控制块就是像是进程的财产登记卡记录着进程所拥有的各项资源只有有了task_struct进程才能被内核所调度】4拥有专有的用户空间【各进程的用户空间是相互独立的但是各进程共享系统空间且各进程不能直接(不通过系统调用)改变系统空间的内容】如果有 1 2 3完全没有用户空间 内核线程(kernel thread比如kswapd)如果有 1 2 3没有独立的用户空间 因为有用户空间但不是独立的所以称为用户线程linux 系统中进程(process)和任务(task)是同一个意思Unix系统的进程在Intel 的技术资料中则称为“任务”linux源自Unix和i386系统结构linux系统运行时的第一个进程是在系统初始化阶段“捏造”出来的而此后的进程或线程则都是由一个已存在的进程像细胞分裂那样通过系统调用复制出来的称为fork或 cloneIntel的i386 通过任务门 和进程的TSS段(任务状态段TSS位于GDT中包含了该进程的关键的状态信息【控制信息】但linux却没有使用任务门)来在硬件上实现任务的切换【但其实因为该处理器的CISC架构以及这种切换方式不是特别的效率高的其实任务切换可以做的更加的简单i386的这个切换可以理解为一种“高级语言”而我们在做操作系统时往往使用效率更高的“低级语言汇编等”】i386 CPU 要求软件去设置TR 与 TSSTR指向CPU当前正在执行的任务进程的TSSIntel的设计意图是随着任务的切换而走马灯似的设置TR的内容CPU因中断或系统调用从用户空间进入系统空间时会由于运行级别的变换而导致自动更换栈不同的栈指针来自于当前任务的TSS中包含的栈指针(SSESP)因为Linux系统中只用到了两个运行级别即0级与3级所以对于内核来说TSS中只剩下0级的堆栈指针 即SS0ESP0Linux系统在任务的切换过程中因为效率的考虑并不根据任务的切换去设置TR而是直接修改TSS(Linux内核只使用这样一个TSS用来保存当前任务的状态)中的SS0ESP0》铁打的营盘流水的兵就一个TSS就像一座营盘建立后就不再动了而里面的内容也就是当前任务的系统堆栈指针则随着进程的调度切换而流水似地变动。这是因为改变TSS中的SS0ESP0所花的开销比通过装入TR以更换一个TSS要小得多。Linux中TSS不是某个进程所独占的他而是全局性的公共资源。内核中虽然有多个TSS但是每个CPU就只有(使用)一个TSS一经装入就不再变了。UnixLinux系统中任务的切换只发生在系统空间中这点很好理解因为共享的系统空间中拥有各个进程的各种资源每个进程都有一个task_struct数据结构和一片用作系统空间栈的存储空间。内核在为每个进程分配一个task_struct结构时实际上分配两个连续的物理页面(共8192个字节)这两个页面的底部用作进程的task_struct结构而在结构的上面就用作进程的系统空间堆栈、数据结构task_struct的大小约为1K字节所以进程的系统空间的堆栈的大小约为7K字节注意系统空间堆栈的空间不像用户空间堆栈那样可以在运行时动态的扩展如第2章所述而是静态的确定了的所以在中断服务程序中、内核软中断服务程序以及其他设备驱动程序的设计中应注意不能让这些函数嵌套太深【避免嵌套太深导致栈的溢出】同时在这些函数中也不适宜使用太多、太大的局部变量。一个进程必定又是一个内核线程(内核线程的要求是1.有代码段 2.有专用的系统栈3.有task_struct数据接否)内核中有一个宏操作current,它指向当前进程task_struct结构的指针。接下来可具体分析下task_struct结构struct task_struct{volatile long state;unsignedlong flags;intsigpending;mm_segment_taddr_limit;structexec_domain *exec_domain;除personality外应用程序还有一些其他的版本间的差异从而形成了不同的“执行域”这个指针就是指向描述本进程所属的执行域的数据结构volatilelong need_resched;unsigned longptrace;int lock_depth;longcounter;long nice;unsignedlong policy;适用于本进程的调度政策详见进程的调度与切换struct mm_struct*mm;int has_cpu,processor;unsigned longcpus_allowed;struct list_headrun_list;unsigned longsleep_time;struct task_struct *next_task,*prev_task;struct mm_struct*active_mm;structlinux_binfmt *binfmt;intexit_code, exit_signal;intpdeath_signal;这三个详见系统调用exit()与wait4()unsignedlong personality;int dumpable:1;int did_exec:1;pid_tpid;进程号pid_tpgrp;pid_ttty_old_pgrp;pid_tpgrp;pid_t tgid;intleader;//pgrppgrpleader当一个用户登陆到系统时就开始一个进程组(session)此后创建的进程都属于这同一个session。此外若干进程可以通过“管道”组合在一起如ls | wc -l从而形成进程组详见“系统调用exec”一节struct task_struct *p_opptr,*p_pptr, *p_cptr, *p_ysptr, *p_osptr;struct list_headthread_group;struct task_struct*pidhash_next;struct task_struct**pidhash_pprev;wait_queue_head_twait_chldexit;struct semaphore*vfork_sem;unsigned long rt_priority;优先级别以及“实时”优先级别详见进程的调度与切换unsigned long it_real_value,it_prof_value, it_virt_value;unsigned long it_real_incr,it_prof_incr, it_virt_incr;struct timer_listreal_timer;struct tms times;unsigned longstart_time;long per_cpu_utime[NR_CPUS],per_cpu_stime[NR_CPUS];unsigned long min_flt, maj_flt,nswap, cmin_flt, cmaj_flt, cnswap;int swappable:1;uid_tuid,euid,suid,fsuid;gid_tgid,egid,sgid,fsgid;这8个主要与文件操作权限有关见文件系统一章int ngroups;gid_tgroups[NGROUPS];kernel_cap_tcap_effective, cap_inheritable, cap_permitted;一般进程都不能为所欲为而是各自被赋予了各种不同的权限。例如一个进程是否可以通过系统调用ptrace()跟踪另一个进程就是由该进程是否具有CAP_SYS_PTRACE授权决定的一个进程是否有权重新引导操作系统则取决于该进程是否具有CAP_SYS_BOOT授权。这样就把进程的各种权限分细了而不再是笼统地取决于一个进程是否是特权用户进程。每一种权限都由一个标志位代表内核中提供了一个inline函数capable()用来检验当前进程是否具有某种权限。如capable(CAP_SYS_BOOT)就是检查当前进程是否有权重引导操作系统〔返回非0表示有权)。值得注意的是对操作权限的这种划分与文件访问权限结合在一起形成了系统安全性的基础。在现今的网络时代这种安全性正在变得愈来愈重要而这方面的研究与发展也是一个重要的课题。intkeep_capabilities:1;structuser_struct *user;指向一个user_struct结构该数据结构代表着进程所属的用户。注意这跟Unix内核中每个进程的user结构时两码事。Linux内核中user结构是非常简单的详见“系统调用fork()”一节。structrlimit rlim[RLIM_NLIMITS];这是一个结构数组表明进程对各种资源的使用数量所受的限制struct rlimit {unsigned long rlim_cur;unsigned long rlim_max;};unsigned shortused_math;char comm[16];int link_count;struct tty_struct*tty;unsigned intlocks;struct sem_undo*semundo;struct sem_queue*semsleeping;struct thread_structthread;struct fs_struct*fs;struct files_struct*files;spinlock_tsigmask_lock;struct signal_struct*sig;sigset_t blocked;struct sigpendingpending;unsigned longsas_ss_sp;size_tsas_ss_size;int (*notifier)(void*priv);void*notifier_data;sigset_t*notifier_mask;u32parent_exec_id;u32self_exec_id;//parent_exec_idself_exec_id与进程组session有关详见系统调用exit()与wait4()spinlock_talloc_lock;}主要可分为状态、性质、资源、和组织等几大类volatile long state;表示进程当前运行的状态#define TASK_RUNNING 0进程处于就绪态()而不是表达该进程就是当前正在运行的进程当进程处于这个状态时内核就将该进程的task_struct结构通过其队列头run_list挂入一个“运行队列”#defineTASK_INTERRUPTIBLE 1进程处于睡眠状态因信号的到来而被唤醒。interruptible_sleep_on()和 wake_up_interruptible()用于浅度睡眠#define TASK_UNINTERRUPTIBLE2进程处于深度睡眠状态不受信号(signal也称软中断)的打扰sleep_on()和wake_up()用于深度睡眠深度睡眠一般只用于临界区和关键性的部位而“可中断”的睡眠那就是通用的特别当进程在“阻塞性”的系统调用中等待某一事件的发生时就不应该进入深度睡眠否则就不能对别的事件作出反应别的进程就不能通过发一个信号来杀掉这个进程这里的INTERRUPTIBLE与UNINTERRUPTIBLE与”中断“毫无关系而是说睡眠能否因其他事件而中断即唤醒不过其他事件主要指”信号“而信号的概率实际上与中断的概率是相同的所以这里所谓的INTERRUPTIBLE也是指这种”软中断“。#define TASK_ZOMBIE 4进程已经去世(exit)但是其户口尚未注销#define TASK_STOPPED 8进程处于就绪态()主要用于调试目的进程接收到一个SIGSTOP信号后就将运行状态改为TASK_STOPPED而进入挂起状态然后在接收到一个SIGCONT信号后又恢复继续运行。unsignedlong flags;;反映进程状态的信息但不是运行状态而是与管理有关的其他信息 ,见下面的注释#define PF_ALIGNWARN0x00000001#define PF_STARTING0x00000002#define PF_EXITING0x00000004#define PF_FORKNOEXEC0x00000040#define PF_SUPERPRIV0x00000100#define PF_DUMPCORE0x00000200#define PF_SIGNALED0x00000400#define PF_MEMALLOC0x00000800#define PF_VFORK0x00001000#define PF_USEDFPU0x00100000再看下task_struct除上述外的其他的一下状态信息变量intsigpending表示进程收到了信号但尚未处理详见进程间通信中的信号一节mm_segment_t addr_limit虚拟地址空间的上限。对进程而言是其用户空间的上限所以是0XBFFFFFFF,对内核线程而言则是系统空间的上限所以是0XFFFFFFFF。volatile longneed_resched与调度有关表示CPU从系统空间返回用户空间前夕要进行一次调度。long counter与调度有关详见进程的调度与切换一节unsigned long personality由于Unix有许多不同的版本和变种应用程序也就有了适用的范围所以根据执行程序的不同每个进程都有其个性。其他的直接看上面的结构里面的注释最后每一个进程都不是孤立地存在于系统中而总是根据不同的目的、关系和需要与其它的进程相联系。从内核的角度看则是要按不同的目的和性质将每个进程纳入不同的组织中。第一个组织是由每个进程的家庭与社会关系形成的宗族或家谱。这是一种树型的组织通过指针p_opptr、p_pptr、p_cptr、p_ysptr和p_osptr构成。其中p_opptr和p_pptr指向父进程的task_struct结构p_cptr指向最年轻的子进程而p_ysptr和p_osptr则分别指向其弟弟和哥哥从而形成一个子进程链。这些指针确定了一个进程在其宗族中的上、下、左、右关系详见本章中对fork()和exit()的叙述。【三个静态队列描述 第一个组织第二个组织第三个组织】这个组织虽然确定了每个进程的宗族关系涵盖了系统中所有的进程但是要在这个组织中根据进程号pid找到一个进程却非易事。进程号的分配是相当随机的在进程号中并不包含任何可以用来找到一个进程的路径信息而给定一个进程号要求找到该进程的task_struct结构却又是常常要用到的一种操作。于是就有了第二个组织那就是一个以杂凑表为基础的进程队列的阵列。当给定一个pid要找到该进程时先对pid施行杂凑计算以计算的结果为下标在杂凑表中找到一个队列再顺着该队列就可以较容易地找到特定的进程了。杂凑表pidhash是在kernel/fork.c中定义的structtask_struct *pidhash[PIDHASH_SZ];杂凑表的大小PIDHASH_SZ为1024。由于每个指针的大小是4个字节所以整个杂凑表(不包括各个队列)正好占一个页面。每个进程的task_struct数据结构都通过其pidhash_next和pidhash_pprev两个指针放入到杂凑表中的某个队列中同一队列中所有进程的pid都具有相同的杂凑值。由于杂凑表的使用要找到pid为某个给定值的进程就很迅速了。当内核需要对每一个进程做点什么事情时还需要将系统中所有的进程都组织成一个线性的队列这样就可以通过一个简单的for循环或while循环遍历所有进程的task_struct结构。所以第三个组织就是这么一个线性队列。系统中第一个建立的进程为init_task这个进程就是所有进程的总根所以这个线性队列就是以init_task为起点(也可以把它看成是一个队列头〕后继每创建一个进程就通过其init_task结构中的next_task和prev_task两个指针链入这个线性队列中。每个进程都必然同时身处这三个队列之中直到进程消亡时才从这三个队列中摘除所以这三个队列都是静态的。在运行的过程中一个进程还可以动态地链接进可执行队列接受系统的调度。实际上这是最重要的队列一个进程只有在可执行队列中才有可能受到调度而投入运行。与前几个队列不同的是一个进程的task_struct是通过其list_head数据结构run_list、而不是个别的指针链接进可执行队列的。以前说过这是用于双向链接的通用数据结构具有一些与之配套的函数或宏操作处理的效率比较高也使代码得以简化。可执行队列的变化是非常频繁的一个进程进入睡眠时就从队列中脱链被唤醒时则又链入到该队列中在调度的过程中也有可能会改变一个进程在此队列中的位置。详见本章进程调度与进程切换以及系统调用nanosleep()中的有关叙述。4.2 进程三部曲创建、执行与消亡就像世上万物都有产生、发展与消亡的过程一样每个进程也有被创建、执行某段程序以及最后消亡的过程。在linux系统中第一个进程是系统固有的、与生倶来的或者说是由内核的设计者安排好了的(系统中第一个建立的进程为init_task这个进程就是所有进程的总根)。内核在引导并完成了基本的初始化以后就有了系统的第一进程(实际上是内核线程)。除此之外所有其它的进程和内核线程都由这个原始进程或其子孙进程所创建都是这个原始进程的后代。在linux系统中一个新的进程一定要由一个已经存在的进程复制出来而不是创造出来(而所谓创建实际就是复制)。所以linux系统(unix也一样)并不向用户(即进程)提供类似这样的系统调用int creat_pro(int (*fn)(void*), void *arg, unsigned long options);可是在很多操作系统(包括一些unix的变种)中都采用了一揽子的方法。它创造出一个进程并使该进程从函数指针数指针fn所指的地方开始执行。根据不同的情况和设计参数fn也可以换成一个可执行程序的文名。这里所谓创造包括为进程分配所需的资源、包括属于最低限度的task_struct数据结构和系统空间堆栈并初始化这些资源还要设置其系统空间堆栈使得这个新进程看起来就好像是一个本来就已经存在而正在睡眠的进程。当这个进程被调度运行的时候其返回地址也就是恢复运行时的下一条指令则就在fn指的地方(uc/os就是这样的)。这个子进程生下来时两手空空却可以完全独立并不与其父进程共享资源。但是linux系统(unix也一样)采用的方法却不同。linux将进程的创建与目标程序的执行分成两步第一步是从已经存在的父进程中像细胞分裂一样地复制出一个子进程。这里所谓像细胞分裂一样只是打个比方实际上复制出来的子进程有自已的task_struct 结构和系统空间堆栈但与父进程共享其它所有的资源。例如要是父进程打开了五个文件那么子进程也有五个打开的文件而且这些文件的当前读写指针也停在相同的地方。所以这一步所做的是复制。linux为此提供了两个系统调用一个是fork()另一个是clone()。两者的区别在于fork()是全部复制父进程所有的资源全都通过数据结构的复制遗传给子进程。而clone()则可以将资源有选择地复制给子进程而没有复制的数据结构则通过指针的复制让子进程共享。在极端的情况下一个进程可以clone()出一个线程。所以系统调用fork()是无参数的而clone()则带有参数。读者也许已经意识到fork()其实比clone()更接近本来意义上的克隆。确实是这样原因在于fork()在unix初期即已存在那时候克隆这个词还不像现在这么流行而既然业已存在就不宜更改了。否则也许应该互换一下名字。后来又增设了一个系统调用vfork()也不带参数但是除task_struct结构和系统空间堆栈以外的资源全都通过数据结构指针的复制遗传所以vfork()出来的是线程而不是进程。读者将会看到vfork()主要是出于效率的考虑而设计并提供的。第二步是目标程序的执行。一般来说创建一个新的进程是因为有不同的目标程序要让新的程序去执行(但也不一定)所以复制完成以后子进程通常要与父进程分道扬镳走自己的路。为此提供了一个系统调用execve()让一个进程执行以文件形式存在的一个可执行程序的映象。读者也许要问这两种方案到底哪一种好应该说是各有利弊。但是更应该说Linux从unix继承下来的这种分两步走并且在第一步中采取复制方式的方案利远大于弊。从效率的角度看分两步走很有好处。所谓复制只是进程的基本资源的复制如task_struct数据结构、系统空间堆栈、页面表等等对父进程的代码及全局变量则并不需要复制而只是通过只读访问的形式实现共享仅在需要写的时候才通过copy_on_write的手段为所涉及的页面建立一个新的副本。所以总的来说复制的代价是很低的但是通过复制而继承下来的资源则往往对子进程很有用。读者以后会看到在计算机网络的实现中以及在client/server系统中的server—方的实现中fork()或clone()常常是最自然、最有效、最适宜的手段。更重要的好处是这样有利于父、子进程间通过pipe来建立起一种简单有效的进程间通信管道并且从而产生了操作系统的用户界面即shell的管道机制。这一点对于unix的发展和推广应用对于unix程序设计环境的形成对于unix程序设计风格的形成都有着非常深远的影响。可以说这是一项天才的发明它在很大程度上改变了操作系统的发展方向。当然从另一角度也就是从程序设计界面的角度来看则一揽子的方案更为简洁。不过fork()加execve()的方案也并不复杂很多。进一步说这也像练武或演戏一样有个固定的招式一旦掌握了以后就不觉得复杂也很少变化了。再说如果有必要也可以通过程序库提供一个一揽子的库函数将这两步包装在一起。创建了子进程以后父进程有三个选择:第一是继续走自己的路与子进程分道扬镳。只是如果子进程先于父进程去世则由内核给父进程发一个报丧的信号。第二是停下来也就是进入睡眠状态等待子进程完成其使命而最终去世然后父进程再继续运行。Linux为此提供了两个系统调用wait4()和wait3()。两个系统调用基本相同wait4()等待某个特定的子进程去世而wait3()则等待任何一个子进程去世。第三个选择是自行退出历史舞台结束自己的生命。为此设置了一个系统调用exit()。这里的第三个选择其实不过是第一个选择的一种特例所以从本质上说是两种选择一种是父进程不受阻的(non_blocking)方式也称为异步的方式另一种是父进程受阻的(blocking)方式或者称为同步的方式。4.3 系统调用fork()vfork() clone()fork()与clone()的区别pid_t fork(void);int clone(int (*fn)(void *arg),void *child_stack, int flags, void *arg);系统调用__clone()的主要用途是创建一个线程这个线程可以是内核线程也可以是用户线程。创建用户空间线程时可以给定子线程用户空间堆栈的位置还可以指定子进程运行的起点。__clone()也可以创建进程有选择地复制父进程的资源。而fork()则是全面地复制。还有一个系统调用vfork()其作用也是创建一个线程但主要只是作为创建进程的中间步骤目的在于提高创建时的效率减少系统开销其程序设计接口则与fork相同。asmlinkage int sys_fork(unsigned long r4, unsigned longr5,unsigned long r6, unsigned long r7,structpt_regs regs){return do_fork(SIGCHLD, regs.regs[15], regs, 0);}asmlinkage int sys_clone(unsigned long clone_flags, unsignedlong newsp,unsigned long r6, unsigned long r7,struct pt_regsregs){if (!newsp)newsp regs.regs[15];return do_fork(clone_flags, newsp, regs, 0);}asmlinkage int sys_vfork(unsigned long r4, unsigned long r5,unsigned long r6, unsigned long r7,struct pt_regs regs){return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD,regs.regs[15], regs, 0);}这三个系统调用 都是 通过调用do_fork()来完成的do_fork()通过不同的参数在函数体内实现相关资源的拷贝下面来解读下这个函数int do_fork(unsigned long clone_flags, unsigned longstack_start,struct pt_regs *regs, unsignedlong stack_size){函数内解释部分参数clone_flags由两部分组成其最低的字节为信号类型用以规定子进程去世时应该向父进程发出的信号。我们已经看到对于fork()和vfork()这个信号就是SIGCHILD而对__clone()则该位段可由调用者决定。第二部分是一些表示资源和特性的标志位(通过这些标志位来采取相应的拷贝动作)对于fork()这一部分为全0表对有关的资源都要复制而不是通过指针共享[位为0表示要复制位为1表示父子先共享]。而对vfork()则为(CLONE_VFORK|CLONE_VM)表示父、子进程共用(用户)虚存区间并且当子进程释放其虚存区间时要唤醒父进程至于__clone()则这一部分完全由调用者设定而作为参数传递下来。其中标志位CLONE_PID有特殊的作用当这个标志位为1时父、子进程〔线程)共用同一个进程号也就是说子进程虽然有其自己的task_structt数据结构却使用父进程的pid。但是只有0号进程也就是系统中的原始进程(实际上是线程)才允许这样来调用__clone()所以564行对此加以检查。接着通过alloc_task_struct()为子进程分配两个连续的物理页面低端用作子进程的task_struct结构高端则用作其系统空间堆栈。注意574行的赋值为整个数据结构的赋值。这样父进程的整个task_struct就被复制到了子进程的数据结构中。经编译以后这样的赋值是用memcpy()实现的所以效率很高。#define CSIGNAL 0x000000ff#define CLONE_VM 0x00000100#define CLONE_FS 0x00000200#define CLONE_FILES 0x00000400#define CLONE_SIGHAND 0x00000800#define CLONE_PID 0x00001000#define CLONE_PTRACE 0x00002000#define CLONE_VFORK 0x00004000#define CLONE_PARENT 0x00008000#define CLONE_THREAD 0x00010000#define CLONE_SIGNAL (CLONE_SIGHAND |CLONE_THREAD)int retval -ENOMEM;struct task_struct*p;DECLARE_MUTEX_LOCKED(sem);if (clone_flags CLONE_PID) {if(current-pid)return -EPERM;}current-vfork_sem sem;p alloc_task_struct();if (!p)goto fork_out;*p *current;retval -EAGAIN;if(atomic_read(p-user-processes) p-rlim[RLIMIT_NPROC].rlim_cur)gotobad_fork_free;atomic_inc(p-user-__count);atomic_inc(p-user-processes);if (nr_threads max_threads)gotobad_fork_cleanup_count;get_exec_domain(p-exec_domain);if (p-binfmt p-binfmt-module)__MOD_INC_USE_COUNT(p-binfmt-module);p-did_exec 0;p-swappable 0;p-state TASK_UNINTERRUPTIBLE;copy_flags(clone_flags,p);p-pid get_pid(clone_flags);函数内解释部分task_struct结构中有个指针user,用来指向一个user_struct结构。一个用户常常有许多个进程所以有关用户的一些信息并不专属于某一个进程。这样属于同一用户的进程就可以通过指针user共享这些信息。显然每个用户有且只有一个user_struct结构。结构中有个计数器__count对属于该用户的进程数量计数。可想而知内核线程并不属于某个用户所以其task_struct中的user指引为0。#define UIDHASH_BITS 8#define UIDHASH_SZ(1 UIDHASH_BITS)static structuser_struct *uidhash_table[UIDHASH_SZ];这是一个杂凑(hash)表。对用户名施以杂凑运算就可以计算出一个下标而找到该用户的user_struct结构。各进程的task_struct结构中还有个数组rlim对该进程占用各种资源的数量作出限制而rlim[RLIMIT_NPROC]就规定了该进程所属的用户可以拥有的进程数量。所以如果当前进程是一个用户进程并且该用户拥有的进程数量已经达到了规定的限制值就再不允许它fork()了.那么对于不属于任何用户的内核线程怎么办呢587行中的两个计数器就是为进程的总量而设的。一个进程除了属于某一个用户之外还属于某个执行域。总的来说Linux是Unix的一个变种并且符合POSIX的规定。但是有很多版本的操作系统同样是Unix变种同样符合POSIX规定互相之间在实现细节上却仍然有明显的不同。这就形成了不同的执行域。如果一个进程所执行的程序是为Solaris开发的那么这个进程就属于Solaris执行域PER_SOLARIS。当然在Linux上运行的绝大多数程序都属于Linux执行域。在task_struct结构中有一个指针exec_domain可以指向一个exec_domain数据结构。常数PID_MAX定义为0X8000。可见进程号的最大值是0X7FFF即32767。进程号0?299是为系统进程(包括内核线程)保留的主要用于各种保护神进程。以上这段代码的逻辑并不复杂我们就不多加解释了。p-run_list.next NULL;p-run_list.prev NULL;if ((clone_flags CLONE_VFORK) || !(clone_flags CLONE_PARENT)) {p-p_opptr current;if (!(p-ptrace PT_PTRACED))p-p_pptr current;}p-p_cptr NULL;init_waitqueue_head(p-wait_chldexit);p-vfork_sem NULL;spin_lock_init(p-alloc_lock);p-sigpending 0;init_sigpending(p-pending);p-it_real_value p-it_virt_value p-it_prof_value 0;p-it_real_incr p-it_virt_incr p-it_prof_incr 0;init_timer(p-real_timer);p-real_timer.data (unsigned long) p;p-leader 0;p-tty_old_pgrp 0;p-times.tms_utime p-times.tms_stime 0;p-times.tms_cutime p-times.tms_cstime 0;#ifdef CONFIG_SMP{int i;p-has_cpu 0;p-processor current-processor;for(i 0; i smp_num_cpus;i)p-per_cpu_utime[i] p-per_cpu_stime[i] 0;spin_lock_init(p-sigmask_lock);}#endifp-lock_depth -1;p-start_time jiffies;函数内解释部分retval -ENOMEM;if (copy_files(clone_flags,p))gotobad_fork_cleanup;if (copy_fs(clone_flags,p))gotobad_fork_cleanup_files;if (copy_sighand(clone_flags,p))gotobad_fork_cleanup_fs;if (copy_mm(clone_flags,p))gotobad_fork_cleanup_sighand;retval copy_thread(0,clone_flags, stack_start, stack_size, p, regs);if (retval)gotobad_fork_cleanup_sighand;p-semundo NULL;p-parent_exec_id p-self_exec_id;p-swappable 1;p-exit_signal clone_flags CSIGNAL;p-pdeath_signal 0;p-counter (current-counter 1) 1;current-counter 1;if(!current-counter)current-need_resched 1;retval p-pid;p-tgid retval;INIT_LIST_HEAD(p-thread_group);write_lock_irq(tasklist_lock);if (clone_flags CLONE_THREAD) {p-tgid current-tgid;list_add(p-thread_group,current-thread_group);}SET_LINKS(p);hash_pid(p);nr_threads;write_unlock_irq(tasklist_lock);if (p-ptrace PT_PTRACED)send_sig(SIGSTOP, p,1);wake_up_process(p);total_forks;fork_out:if ((clone_flags CLONE_VFORK) (retval 0))down(sem);return retval;bad_fork_cleanup_sighand:exit_sighand(p);bad_fork_cleanup_fs:exit_fs(p);bad_fork_cleanup_files:exit_files(p);bad_fork_cleanup:put_exec_domain(p-exec_domain);if (p-binfmt p-binfmt-module)__MOD_DEC_USE_COUNT(p-binfmt-module);bad_fork_cleanup_count:atomic_dec(p-user-processes);free_uid(p-user);bad_fork_free:free_task_struct(p);goto fork_out;}4.4 系统调用execve()在大多数情况下如果复制出来的子进程不能与父进程分道扬镳走自己的路那就没有多大的意思所以执行一个新的可执行程序是进程生命历程中关键性的一步。linux为此提供了一个系统调用execve()而在C语言的程序库中又在此基础上向应用程序提供一整套的库函数包括execl()execlp() execleo() execv() execvp()系统调用execve()内核入口是sys_execve()。sys_execve()就调用do_execve(),以完成其主体部分的工作。显然先要将给定的可执行程序文件找到并打开do_execve()就是为此而调用的.当目标文件已经打开下一步就要从文件中装入可执行程序了。内核中为可执行程序的装入定义了一个数据结构linux_binprm,这个数据结构将运行一个可执行文件时所需的信息组织在一起。structlinux_binprm{char buf[BINPRM_BUF_SIZE];struct page *page[MAX_ARG_PAGES];unsigned long p;int sh_bang;struct file * file;int e_uid, e_gid;kernel_cap_t cap_inheritable, cap_permitted,cap_effective;int argc, envc;char * filename;unsigned long loader, exec;}int do_execve(char * filename, char ** argv, char ** envp,struct pt_regs * regs){struct linux_binprm bprm;struct file *file;int retval;int i;file open_exec(filename);retval PTR_ERR(file);if (IS_ERR(file))return retval;bprm.p PAGE_SIZE*MAX_ARG_PAGES-sizeof(void *);memset(bprm.page, 0,MAX_ARG_PAGES*sizeof(bprm.page[0]));bprm.file file; --保存打开文件的file结构指针bprm.filename filename;bprm.sh_bang 0;--bprm.loader 0;bprm.exec 0;if ((bprm.argc count(argv, bprm.p / sizeof(void *))) 0){allow_write_access(file);fput(file);return bprm.argc;}if ((bprm.envc count(envp, bprm.p / sizeof(void *))) 0){allow_write_access(file);fput(file);return bprm.envc;}retval prepare_binprm(bprm);if (retval 0)goto out;retval copy_strings_kernel(1, bprm.filename,bprm);if (retval 0)goto out;bprm.exec bprm.p;retval copy_strings(bprm.envc, envp, bprm);if (retval 0)goto out;retval copy_strings(bprm.argc, argv, bprm);if (retval 0)goto out;retval search_binary_handler(bprm,regs);if (retval 0)return retval;out:allow_write_access(bprm.file);if (bprm.file)fput(bprm.file);for (i 0 ; i MAX_ARG_PAGES ; i) {struct page * page bprm.page[i];if (page)__free_page(page);}return retval;}