相关的系统调度
- fork(): 用于创建进程,Linux中进程的创建者与被创建者是父子关系
- clone():克隆,真正实现创建进程的操作,fork()是不带参数的,clone()是带参数的,通过不同的参数后,可以不仅仅做fork(),也能做其他的创建进程的方式
- exec():加载外部存储器的一段代码,把一个可执行文件,加载到当前的进程执行环境中。往往是和fork()和clone搭配使用的。
- exit():退出;进程生命周期结束就会执行exit(),消亡时exit()会把这个信息通知父进程,父进程根据这些信息做出相应的反映。
- wait4(): 子进程的运行情况需要通过一些方式将信息反馈给父进程,这个系统调用和反馈信息密切相关;等待相应的子进程反馈相应的数据状态信息
- waitpid():同上
进程描述符
进程描述符是用来在操作系统中标识一个进程的
slab机制
slab机制的linux进行进程描述符分配的一种机制。
比如土司面报被切成很多片,每一块就是slab.
内存区间通过动态分配机制分配下来,这个区间按照一定的结构尺寸切成若干份,在使用时就在若干份存储区间中去申请,每次去分配一块,每块对应的就是具体应用的数据结构的尺寸;应用在进程描述符时,每个slab的尺寸就是进程描述符的尺寸。
Linux为什么用这种机制而不是使用动态分配内存呢?
针对资源要被频繁使用归还的使用slab机制就可以提升很大的效率
分配进程描述符
2.6内核之前,在每个进程内核栈末端来分配。
进程的状态
- TASK_RUNNING:就绪或运行
- TASK_INTERRUPTIBLE:可打断的;如果进程执行中阻塞了,这个阻塞状态的是可以打断的,有两种情况可以唤醒:1.获取到了它等待的资源;2.它没有得到想要的资源,但系统通过其他的方式唤醒它,唤醒时系统会检测它是以哪种方式被唤醒的从而做不同的处理方式。
- TASK_UNINTERRUPTIBLE:不可打断的;当进程阻塞时,唤醒它的唯一方式就是等待的资源被满足了。
- _TASK_TRACED:做跟踪,调试相关
- _TASK_STOPPED:也是调试相关
总结:
- linux进程状态没有 就绪 这个状态 ;就绪 和 执行 状态都是 TASK_RUNNING;就绪到执行需要通过调度的方式进行上下文切换
- linux中某个进程(任务 task)发生了睡眠,或者在某个队列中等待某些资源,就要进入TASK_INTERRUPTIBLE;就是阻塞状态。
- linux中,进程消亡时释放了代码空间,数据空间等,但在内核中的资源是没有释放的,比如进程描述符;所以linux提供了一个僵死的状态。进程在Linux中创建是父子的关系,父进程创建子进程后,子进程执行完毕后,父进程是需要知道子进程是否终止以及以什么方式终止等状态信息的,linux的设计理念是进程终止之后会对父进程发送一个信号,告诉他我这个进程已经结束了,这是父进程可以通过系统提供的数据结构(比如子进程的进程描述符)来获取子进程执行过程中的状态信息,直到父进程觉得没必要保留这个信息时会通知内核,让内核把这个子进程的数据都释放掉。
设置进程的状态:
<linux/sched.h>中
set_task_state(task,state);
task->state=state;
更清晰的一个
进程上下文
进程上下文指的是整个进程和他相关的自身的和周围环境的整个场景是什么。
一个进程的代表是进程描述符
- 疑问1:可执行代码是如何组织的?
- 疑问2:相关的数据如何组织?
用户空间与内核空间
- 用户进程在哪个空间?
- 是否进入内核空间?
- 如何进入内核空间?
用户的代码是不能进入到内核空间去执行的,也不能访问内核空间的数据,但可以通过系统调用与内核发生关系。这个系统调用是在内核空间执行的,而它代表的是当前的这个进程,所以进程上下文仍然是当前的进程上下文。
创建进程
copy-on-weite:
原因:大多数时子进程都不需要父进程的那些代码和资源,但fork()是直接父进程创建出一个和自己一摸一样的子进程,所以如果创建时直接把自己的代码段和数据段复制一份出来是效率很低的。
策略:当子进程被创建出来时,它可以共享父进程的代码段和数据段,当子进程要对这块共享的内存进行写操作时,再进行拷贝操作。这就是一种延迟的拷贝策略,这样就最大限度的保证了创建进程的执行效率。
fork()--调用--clone()--调用--do_fork()--调用--copy_process()
除了写时拷贝还有个优化拷贝的策略
linux中的线程
进程终结
孤儿进程和僵尸进程
关于僵尸进程和孤儿进程有一个不错的总结: