Linux内核设计与实现——进程管理(续)
线程在Linux中的实现
- 线程机制是现代编程技术中常用的一种抽象概念;该机制提供了在统一程序内共享内存地址空间运行的一组线程。这些线程还可以共享打开的文件和其他资源。线程机制支持并发程序设计技术,在多处理器系统上,能保证真正的并行处理
- Linux把所有的线程都当做进程来实现,被视为一个与其他进程共享某些资源的进程
创建线程
-
线程的创建和普通进程的创建类似,只不过在调用clone()的时候需要传递一些参数标志来指明需要共享的资源
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONES_SIGHAND,0);
该代码产生的结果和fork()差不多,只是父子俩共享地址空间、文件系统资源、文件描述符和信号处理程序
-
传递给clone()的参数标志决定了新创建进程的行为方式和父子进程之间共享的资源种类
内核线程
-
内核经常需要在后台执行一些操作。这种任务可以通过内核线程完成:独立运行中内核空间的标准进程
-
内核线程和普通的进程间的区别在于内核线程没有独立的地址空间,他们只在内核空间运行,从来不切换到用户空间去
-
内核线程和普通进程一样,可以被调度,也可以被抢占
-
内核线程只能由其他内核线程创建,创建方法:
struct task_struct *kthread_create(int (*threadfn)(void *data), void *data, const char namefmt[], ...)
进程终结
-
一般来说,进程的析构是自身引起的。发生在进程调用exit()系统调用时
-
当进程接受到它既不能处理也不能忽略的信号或异常时,它还可能被动地终结
-
不管进程怎么终结的,该任务大部分都要靠do_exit()来完成:
- 将task_struct中的标志成员设置为PE_EXITING
- 调用del_timer_sync()删除任一内核定时器。根据返回的结果,确保没有定时器在排队,也没有定时器处理程序在运行
- 如果BSD的记账功能是开启的,do_exit()调用acct_update_integrals()来输出记账信息
- 然后调用exit_mm()函数释放进程占用的mm_struct,如果没有别的进程使用他们,就彻底释放他们
- 接下来调用sem__exit()函数。如果进程排队等候IPC信号,它则离开队列
- 调用exit_files()和exit_fs(),以分别递减文件描述符、文件系统数据的引用计数。如果其中某个引用计数的数值降为零,那么就代表没有进程在使用相应的资源,此时可以释放
- 接着把存放在task_struct的exit_code成员中的任务退出代码置为由exit()提供的退出代码,或者去完成任何其他由内核机制规定的退出动作。退出代码存放在这里供父进程随时检索
- 调用exit_notify()向父进程发送信号,给子进程重新找养父,养父为线程组中的其他线程或者为init进程,并把进程状态设成EXIT_ZOMBIE
- do_exit()调用schedule()切换到新的进程。因为处于EXIT_ZOMBIE状态的进程不会再被调度,所以这是进程所执行的最后一段代码。do_exit()永不返回
-
至此,与进程相关联的所有资源都被释放掉了。进程不可运行并处于EXIT_ZOMBIE退出状态。它占用的所有内存就是内核栈、thread_info结构和tast_struct结构。此时进程存在的唯一目的就是向它的父进程提供信息。父进程检索到信息后,或者通知内核那是无关的信息后,由进程所持有的剩余内存被释放,归还给系统使用
删除进程描述符
-
调用do_exit()后,系统还保留着僵死进程的的进程描述符,在父进程获得已终结的子进程的信息后,或者通知内核它并不关注那些信息后,子进程的task_struct结构才被释放
-
wait()这一族函数都是通过唯一(但很复杂)的一个系统调用wait4()来实现的;它的标准动作是挂起调用它的进程,知道其中的一个子进程退出,此时函数会返回该子进程的PID
-
当最终需要释放进程描述符时,release_task()会被调用,完成以下工作:
- 它调用__exit_signal(),该函数调用_unhash_process(),后者又调用detach_pid()从pidhash上删除该进程,同时也要从任务列表中删除该进程
- _exit_signal()释放目前僵死进程所使用的所有剩余资源,并进行最终统计和记录
- 如果这个进程是线程组最后一个进程,并且领头进程已经死掉,那么release_task()就要通知僵死的零头进程的父进程
- release_task()调用put_task_struct()释放进程内核栈和thread_info结构所占的页,并释放task_struct所站的slab告诉缓存
-
至此,进程描述符和所有进程独享的资源就全部释放掉了
孤儿进程造成的进退维谷
- 如果父进程在子进程之前退出,必须有机制来保证子进程能找到一个新的父亲,否则这些成为孤儿的进程就会在退出时永远处于僵死状态,白白耗费内存
- 解决方法是给子进程在当前进程组内找一个线程做父亲,不行的话就让init做他们的父进程
end
进程管理这一节就有点力不从心了,纯理论还是不能留下深刻的印象,之后结合实践再了解了解吧