文章目录
进程描述符及任务结构
内核把进程的列表存放在任务队列task list中,是双向循环链表,链表的每一项是task_struct结构,称为进程描述符。task_struct在32位机器上越占1.7KB。
访问进程描述符是一个重要且频繁的操作。
slab分配task_struct,修改pid_max
task_struct由slab分配器动态分配,thread_info结构包含指向task_struct的一个指针域task。每个任务的thread_info结构在它的内核栈的尾端分配。
实际上在内核大部分处理进程的代码都是直接通过task_struct进行。
PID最大默认值为32768,short int型,可以通过/proc/sys/kernel/pid_max修改。
current宏
current宏,是一个全局指针,指向当前进程的struct task_struct结构体,即表示当前进程。硬件体系结构不同其实现方式不同:x86在内核栈的尾端创建thread_info结构,通过计算偏移查找task_struct;PowerPC有专用的寄存器用于保存task_struct的指针。
进程上下文
当一个程序执行了系统调用或者触发了某个异常,它就陷入了内核空间。此时内核 “代表进程执行” 并处于进程上下文中,在此上下文中current宏有效,如果在此间隙没有更高优先级进程执行,那么内核退出后,程序会继续在用户空间执行。
对内核的所有访问都必须通过接口(系统调用和异常处理程序)。
进程上文
进程上下文中(当程序系统调用或触发异常时)[内核空间]
进程下文
进程家族
进程之间存在继承关系,所有进程都是PID为1的init进程的后代。
init进程的进程描述符是init_task静态分配。内核中init_task变量就是是进程0使用的进程描述符,也是Linux系统中第一个进程描述符,init_task并不是系统通过fork、kernel_thread的方式,而是由内核黑客静态创建的。
内核在系统启动的最后阶段启动init进程,然后由init进程读取系统的初始化脚本initscrip并执行其他相关程序,完成系统启动的整个过程。
进程创建
其他操作系统提供产生进程的机制spawn,首先在新的地址空间创建进程,再读入可执行文件,最后执行。
在linux中,通过两步fork()和exec()实现:
fork拷贝当前进程创建子进程,子父进程区别在于pid、ppid、某些资源、统计量。
写时拷贝copy on write
父子进程共享一个拷贝,资源的复制只有在写时才进行,在此之前是只读方式共享。
fork的实际开销是复制父进程的页表 和 给子进程创建进程描述符task_struct。
fork()
clone()
do_fork()
线程在Linux中的实现
线程机制,提供在同一程序内共享内存地址空间运行,还可以共享打开的文件和其他资源。支持并发程序设计技术(concurrent programming),再多处理器上支持真正并行(parallelism)。
在Linux中,从内核的角度看并没有线程这个概念,把所有的线程都当进程实现。线程被视为一个与其他进程共享某些资源的进程。
线程创建
与进程创建类似,只是在调用clone()时需要传递一些参数标志指明需要共享的资源。在<linux/sched.h>中定义。
内核线程
内核线程是:独立运行在内核空间的标准进程。
与普通进程的不同,是没有独立的地址空间(指向地址空间的mm指针被设置为NULL)。
内核线程只能由其他内核线程创建。所有新的内核线程由kthreadd内核进程衍生出。
创建内核线程:kthread_create(创建)、kthread_run(创建并运行)
退出:do_exit()(自己退出)、kthread_stop(task_struct)(内核其他部分调用退出)
进程终结
内核必须释放进程所占用的资源 并 告知其父进程。
进程显示或者隐式调用exit()系统调用结束进程。
exit()
do_exit()
do_exit()结束后进程的描述符还保留着。进程结束所需的清理工作 和 删除描述符是分开的两个工作。
孤儿进程
父进程已经退出。
父进程退出,子进程会在当前进程组找一个新的父,如果不行就把init作为其父进程。
exit()
do_exit()
exit_notify()
forget_original_parent()
find_new_reaper()