进程管理
-
进程是处于执行期的程序以及相关的资源的总称,也称作任务。
- 可以两个或两个以上的进程执行同一个程序
- 也可以两个或两个以上并存的进程共享许多资源
-
执行线程,简称线程,是在进程中活动的对象。
-
内核调度的对象是线程,而不是进程。
进程描述符及任务结构
-
内核把进程的列表存放在任务列表(task list)的双向循环链表中。
- 链表中每一项都是类型为task_struct的进程描述符的结构。
-
进程描述符中包含的数据能完整地描述一个正在执行的程序:
- 打开的文件
- 进程的地址空间
- 挂起的信号
- 进程的状态
- more
分配进程描述符
- Linux通过slab分配器分配task_struct结构-->达到对象复用和缓存着色的目的
- 每个任务的thread_info结构在它的内核栈的尾端分配。
进程描述符的存放
- 内核通过一个唯一的进程标识值或PID来标志每个进程。
- PID是一个数,表示为pid_t隐含类型(其物理表示是未知或不相关的),实际上是一个int类型的。
进程状态
-
存在五种进程状态标志:
- TASK_RUNNING(运行)
- TASK_INTERRUPTIBLE(可中断)
- TASK_UNINTERRUPTIBLE(不可中断)
- _TASK_TRACED(被追踪)
- _TASK_STOPPED(停止)
-
各种状态之间存在转换的条件,若满足则进行转换,跟模拟电路中所学的状态机是一个道理。
-
通过st_task_state(task,state)函数来设置当前进程状态
进程家族树
- Unix系统的进程之间存在一个明显的继承关系
- 在Linux中,所有的进程都是PID为1的init进程的后代。(在孤儿进程处理时,为其找新的父亲,则可将其设为init进程的子程序)
- 系统中的每个进程必有一个父进程
- 每个进程可以没有子进程也可以有多个子进程。
- 拥有同一个父进程的所有进程被成为兄弟。
- 每个task_struct都包含一个指向其父进程task_struct、叫做parent的指针,还包含一个成为children的子进程链表。
进程创建
- 产生进程的机制:首先在新的地址空间里创建进程,读入可执行文件,最后开始执行。
- fork和exec两个单独的函数来完成上句前半部分和后半部分的内容。
- fork函数所创建的子进程和父进程之间的区别在于PID、PPID和某些资源和统计量。
- fork进程通过拷贝当前进程创建一个子进程。
- exec负责读取可执行文件并将其载入地址空间开始执行。
写时拷贝
- 资源的复制只有在需要写入的时候才进行,在此之前,知识以只读方式共享。
- fork的实际开销就是复制父进程的页表以及给子进程创建唯一的进程描述符。
fork
- clone通过系统调用实现fork,do_fork完成了创建中的大部分工作,它被定义在了kernel/fork.c文件中。
- do_fork调用copy_process函数,让进程开始运行。
- vfork与fork的区别在于:vfork不拷贝父进程的页表项。
创建线程
- 线程的创建和普通进程的创建类是,只不过在调用clone的时候需要传递一些参数标志来指明需要共享的资源。
- 参数标志如下:
- 内核线程和普通的进程间的区别在于内核线程没有独立的地址空间,它们只在内核空间运行,从来不切换到用户空间去。
- 内核进程和普通进程一样,可以被调度,也可以被抢占。
进程终结
- do_exit完成终结进程的大部分任务,定义于kernel/exit.c
- do_exit永不返回。
- 进程终止时所需要的清理工作和进程描述符的删除被分开执行。
- wait这一族函数都是通过唯一的系统调用wait4()来实现的。
- 当最终需要释放进程描述符时,release_task会被调用。
孤儿进程造成的进退维谷
- 如果父进程在子进程之前退出,则会造成子进程没有父进程,则被成为孤儿进程。
- 若这些孤儿进程无法找到新的父进程,则它就会在退出的时候永远处于僵死的状态,白白消耗内存空间。
- 解决的办法就是为其找到一个新的父进程,若不行,则让init做为它们的父进程。