用户使用计算机的接口是用户程序,进程管理是所有操作系统的心脏,linux也不例外。
一、进程
1.进程描述
进程是正在执行的程序代码的实时结果。为表示正在运行的程序,进程除了包含可执行程序代码,还需要包含其他资源:
进程包含的其他资源 |
---|
打开的文件 |
挂起的信号 |
内核内部数据 |
处理器状态 |
一个或多个具有内存映射的内存地址空间及一个或多个执行线程 |
存放全局变量的数据段 |
执行线程是进程中活动的对象,内核调度的对象是线程而不是进程。每个线程都拥有一个
独立的程序计数器 |
---|
进程栈 |
一组进程寄存器 |
进程提供两种虚拟机制:虚拟处理器和虚拟内存。虚拟处理器给进程一个假象,让进程觉得自己在独享处理器。线程之间可以共享虚拟内存,但每个都拥有各自的虚拟处理器。实际上,可能存在两个或多个不同的进程执行同一个程序,共享许多诸如打开的文件、地址空间之类的资源。
1.1 进程描述符及任务结构
内核把进程的列表存放在任务队列(task list)的双向循环链表中,链表项是类型为task_struct的进程描述符。
进程描述符包含的数据能完整地描述一个正在执行的程序:它打开的文件、进程的地址空间、挂起的信号、进程的状态等。
2.进程运行
a.进程状态
系统中每一个进程必然处于某一状态,表示当前运行情况。
b.进程上下文
进程只有通过系统调用和异常处理程序接口才能陷入内核执行-------对内核的所有访问必须通过这些接口。
可执行程序代码从可执行文件载入到进程地址空间,一般在用户空间执行。也可能执行系统调用或触发了某个异常,这时进程陷入内核空间。内核代表进程执行并处于进程上下文中,退出时恢复用户空间并继续执行。
c.进程家族树
进程之间存在一个集成关系,所有进程都是PID为1的init进程的后代。内核在系统启动最后阶段启动init进程,读取初始化脚本并执行其他的相关程序,最终完成系统的整个过程。
系统中的每个进程必有一个父进程,每个进程也可以拥有零个或多个子进程。拥有同一个父进程的所有进程被称为兄弟。
进程间的关系存放在进程描述符中,task_struct包含一个指向父进程的task_struct类型的指针parent,称为children的子进程链表。
二、进程创建
linux产生(spawn)进程的机制被分解到两个单独的函数fork()和exec(),fork()通过拷贝当前进程创建子进程,exec()读取可执行文件并将其载入地址空间开始执行。
fork()使用写时拷贝页实现,将地址空间上的页拷贝推迟到实际发生写入的时候才进行。fork()的实际开销是复制父进程的页表以及给子进程创建唯一的进程描述符。进程创建后马上运行一个可执行文件,这种优化可以避免拷贝大量根本不会被使用的数据,提高快速执行的能力。
1.fork()
fork()、vfork()、__clone()库函数都根据各自需要的参数标志调用clone(),然后由clone()去调用do_fork().do_fork()完成了创建中的大部分工作,该函数调用copy_process()函数,然后让进程开始运行。copy_process()函数工作过程如下:
2.线程在linux中的实现
线程机制是现代编程技术中常用的抽象概念。该机制使得一组线程可以在一个程序内共享内存地址、打开的资源和其他资源;线程机制用于支持并发程序设计技术,保证真正的并行处理。
clone(CLONE_VM|CLONE_PS|CLONE_FILES|CLONE_SIGHAND,0)
内核线程是独立运行于内核空间的标准进程,可以让内核在后台执行一些操作。内核线程和普通进程区别在于内核线程没有独立的地址空间,只在内核空间运行,从来不切换到用户空间去。内核进程和普通进程一样,可以被调度和抢占。
内核线程有很多可以通过ps -ef查看,它们系统启动时由另外一些内核线程创建。内核从kthreadd内核进程中衍生出所有内核线程。
从现有内核线程创建一个新的内核线程:
struct task_struct *kthread_create(int (*threadfn)(void *data),
void *data,
const char
namefmt[],
…);
创建一个进程并让它运行起来:
struct task_struct *kthread_run(int (*threadfn)(void *data),
void *data,
const char
namefmt[],
…);
退出内核线程
调用do_exit()或内核其它线程调用kthread_stop()
三、进程终结
进程的终结是自身引起的,它发生在进程调用exit()系统调用时,既可能显示地调用这个系统调用,也可能隐式地从某个程序的主程序返回(c语言编译器会在main()函数的返回点放置调用exit()的代码)。当进程接受到它既不能处理也不能忽略的信号或异常时,还可能被动的终结。不管进程怎么终结的,该任务大部分都要依靠do_exit()来完成。
至此,进程不可运行并处于EXIT_ZOMBIE退出状态,与进程相关的所有资源都被释放掉了。
进程终结所需清理工作和进程描述符删除分开进行。此时进程存在的唯一目的是向它的父进程提供信息,占有的所有内存就是内核栈、thread_info结构和task_struct结构,父进程检索检索到信息或者通知内核那是无关信息后,由进程所持有的剩余内存被释放,归还给系统使用。
孤儿进程
父进程在子进程之前退出,必须有机制保证子进程找到一个新的父亲,否则孤儿进程在退出时永远处于僵死状态,白白地浪费内存。
解决办法是给子进程在当前线程组内找一个线程作为父亲,否则就让init做它们的父进程。