1 进程管理相关代码
1.1 thread_info结构,在文件<asm/thread_info.h>中定义
struct thread_info {
struct task_struct *task;
struct exec_domain *exec_domain;
unsigned long flags;
__u32 cpu;
__s32 preempt_count;
mm_segment_t addr_limit;
u8 supervisor_stack[0];
};
每个任务的thread_info结构在其内核栈顶端分配,其task域指向该任务的事件task_struct结构。
1.2 任务队列(task list)
双向循环链表,链表每一项都是一个task_struct(定义在include/linux/sched.h中),该进程描述符包含了一个仅进程的所有信息。
1.3 slab
linux通过slab机制分配task_struct结构,以达到对象复用和缓存着色的目的。
1.4 pid
linux通过pid(pid_t类型,integer)来标示每一个任务进程。为了与老版本unix和linux兼容,pid最大值为32767.如果需要更多进程,可以通过
修改/proc/sys/kernel/pid_max来提高上限。
1.5 current宏
current宏用于查找当前正在运行进程的进程描述符。不同的硬件体系结构的实现不同。
在x86架构中,寄存器不多,不能使用专门的寄存器指向当前进程的task_struct.它的方式是通过在内核栈顶端创建thread_info结构,间接的
查找task_struct结构:
(1)通过屏蔽栈指针(esp)的后13个有效位获得thread_info结构,在current_thread_info()函数完成:
movl $-8192, %eax ;(-8192)十进制 = (...110 000 000 000 000)二进制
andl %esp, %eax
(2)从thread_info结构的task域中获得task_struct的地址:
current_thread_info()->task;
而在PowerPc机构上,使用了一个专门的寄存器来保存指向当前task_struct结构的地址。
1.6 set_task_state(task, state)函数
用于调整指定的进程状态。set_current_state(state)和set_task_state(current,state)含义相同。
2 进程状态
。TASK_RUNNING, 进程可执行;进程或者正在执行,或者在运行队列中等待执行;
。TASK_INTERRUPTIBLE,进程被阻塞,等待某些条件的达成。此外可以被进程唤醒并执行;
。TASK_UNINTERRUPTIBLE, 与可终端状态完全相同,除了它不可以被进程唤醒,并因此使用较少;
。TASK_ZOMBIE,进程已结束,但是父进程还未调用wait4()系统调用。为了让父进程能够获知它的消息,子进程的进程描述符仍然被保留;
。Task_STOPPED,进程停止执行,由于接受到信号引起,如SIGSTOP,SIGTSTP等。在调式期间,任何信号都会使进程进入该状态。
3 进程创建
unix的进程创建很特别。许多别的操作系统产生进程都是在新的地址空间创建进程,读入可执行文件,然后开始执行。而
unix则是使用fork()函数拷贝当前进程创建一个子进程,子进程与父进程的区别仅仅在于pid和ppid(父进程id)和某些
资源和统计量,然后调用exec()函数读取可执行文件并将其载入地址空间开始运行。
两种方法达到的效果是类似的。
3.1 写时拷贝
传统的flok()系统调用将所有资源复制给新创建的进程,此实现效率低下。linux的fork()采用了写时拷贝,一种
推迟甚至免除拷贝数据的技术。
调用fork()函数时,父子进程共享同一个资源拷贝而并不复制。只有在需要对资源进行写入时,数据才会被复制,以使
父子进程拥有不同的拷贝。当资源根本不会被写入的情况下(例如fork()之后立即调用exec()),他们就根本无须复制了,
使得fork()的实际开销就是赋值父进程的也表以及给子进程创建进程描述符,避免了大量无用拷贝,提高效率。
3.2 fork()
fork()函数通过调用系统调用clone(),clone()再调用do_fork(),do_fork()再调用copy_process(),最
后是进程开始运行。
clone()系统调用通过一系列的参数标志来知名父子进程需要共享的资源。fork(),vfork(),__clone()等库函数
都根据各自需要的参数标志去调用clone(),然后根据上面的调用函数链完成创建工作。
下面树copy_process()函数的工作:
。 调用dupy_task_struct()为新进城创建一个内核栈,thread_info和task_struct,此时父子进程拥有相同值;
。 检查当前用户所拥有的进程数目没有超过分配的资源限制;
。 区分父子进程,子进程描述符内的许多成员被清0或设为初始值;
。 设置子进程的状态为TASK_UNINTERRUPTIBLE以保证它不会投入运行;
。 调用copy_flags()更新task_struct的flags成员。超级用户权限标志PF_SUPERPRIV标志清0,表明进程还没有
调用exec()函数的PF_FORKNOEXEC标志被设置;
。 调用get_pid()为新进程获取一个有效的pid;
。 根据传递给clone()的参数标志,拷贝或共享打开的文件,文件系统信息,信号处理函数,进程地址空间和命名空间等。
。 让父子进程平分剩余的时间片;
。 完成扫尾工作并返回一个指向子进程的指针。
在do_fork()函数中,如果调用copy_process()函数成功返回,则内核唤醒子进程并选择父子进程中的子进程先运行。因为
一般子进程都会马上调用exec()函数,从而充分利用写时拷贝。避免附近城向地址空间写入数据而导致的不必要拷贝。
3.3 vfork()
vfork()和fork()功能类似,除了不拷贝父进程的页表项。子进程作为父进程的一个单独的线程在它的空间里运行,父进程
被阻塞,直到子进程退出或执行exec(),子进程不能向地址空间写入。
在原来没有写时拷贝时,效果很好。现在,意义不是很大。