进程管理的任务
进程能创建新的进程 (真正做到一个应用多进程)
确定哪个进程能够拥有CPU
接受中断并将中断导向相应的内核子系统
向用户进程发送信号
管理时钟硬件
当一个进程结束时,释放其资源
动态装载执行模块
相关概念简介
进程就是处于执行期的程序。包括代码段,占用的资源(打开的文件,挂起的信号,内核内部数据,处理器状态,内存地址空间)
线程,执行线程,是在进程中活动的对象。拥有独立的程序计数器,进程栈,寄存器组。
内核调度的对象是线程,不是进程。
Linux线程实现很特别,对线程和进程并不特别区分。??只是在资源共享上有区别??
进程描述符 PCB 在linux内核中的数据结构是task_struct 约1.7KB
进程家族树 : 所有的进程都是PID为1的init进程的后代,内核在系统启动的最后阶段启动init进程。Init进程描述符是作为init_task静态分配的。
进程的状态:
task_struct的state域中:
- TASK_RUNNING: 进程是可执行的进程要么在执行,要么在运行队列中等待执行
- TASK_INTERRUPTIBLE:可中断。进程正在睡眠,等待某些条件的达成。条件达成,内核就会把进程状态设置为运行。也可以接收到信号而提前被唤醒。
- TASK_UNINTERUPTIBLE:不可中断。接收到信号也不会投入运行。其他和可中断相同。
- __TASK_TRACED:被其他进程跟踪
- __TASK_STOPPED:进程停止。进程没有投入运行,也不能投入运行。在接受到SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU等信号时调试期间收到这种信号,都会进入这种状态。
- TASK__ZOMBIE僵尸,呆傻状态,已结束,但其父进程未接到通知,描述符未释放
- TASK_SWAPPING进程页面被兑换出内存
- task_struct的exit_state域中有EXIT_ZOMBIE
[ 状态转换图 ]
进程的管理
创建
fork系统调用,复制现有进程,可以接着调用exec函数组创建新的地址空间,并把新程序载入。
fork提高效率的措施
- 分配进程描述符:
预分配描述符(SLAB机制),把动态分配的过程省略掉一部分(不需要频繁调用内存管理相应功能),相当于一种高级缓存,提高效率。最后的操作主要是直接填写结构。 - COPY_ON_WRITE :
写时copy LINUX的FORK()过程并未为新生成的进程马上复制代码,开始的进程仅仅读共享父进程代码。直到进程第一次要对进程空间有写请求时,再复制代码。 这样做的好处:效率高(一般新进程要有自己的代码,第一条就是EXEC())
退出
程序通过exit系统调用退出执行。
进程退出(释放资源)的过程exit
- task_struct 标志成员设置为PF_EXITING
- 调用del_timer_sync()删除任一内核定时器。
- 根据返回的结果确定,确保没有定时器在排队,也没有定时器程序在运行。
- 如果BSD的进程记账功能开启,do_exit调account_update_integrals来输出记账信息
- 调用exit_mm(),释放进程占用的mm_struct,如果没有别的进程使用,彻底释放他们。
- 调用sem__exit(). 如果进程排队等候IPC信号,则离开队列
- 调用exit_files(),exit_fs(),递减文件描述符,文件系统的引用计数。如果技术为0,释放资源。
- task_struct 的 exit_code成员中的退出代码替换为EXIT()提供的代码
调用exit_notify()向父进程发信号,给子进程寻找养父,进程状态设置为EXIT_ZOMBIE
- 调用shedule切换到其他进程
删除进程描述符:
父进程wait,方法返回后根据返回代码执行相应动作或者忽视。动作完成后真正释放描述符。
调用relase_task
1. release调用__exit_signal,该函数调用_unhashprocess(),后者从pidhash上删除该进程。
2. 如果这是线程组的最后一个进程,且领头进程死掉,通知僵死的领头进程的父进程
3. 调用put_task_struct释放描述符,内核栈,threadinfo所占的页,slab高速缓存。
进程描述符的存放,使用current宏或者专门寄存器
参考 《linux内核设计与实现》