进程
进程是什么?进程是操作系统分配内存、CPU等资源的基本单位。线程这是进程的进一步抽象,一个进程可以分为线程与资源集合。
在Linux中,并没有进程与线程的严格区分,在内核看来线程与进程一样,只不过是与其他一些进程共享内存等某些资源。
进程描述符
在Linux内核中,通过task_struct
这个结构体来描述进程,其中包含了许多有关进程的信息,例如进程的状态、线程信息、优先级等。
进程状态
运行TASK_RUNNING
这个状态说明进程可以获取时间片来运行,但是并不意味着该进程的指令正在执行或者获得了CPU资源,只能说是可以被调度执行。
可中断等待TASK_INTERRUPTINLE
进程处于等待状态,不会被调度执行。当等待的资源可用,系统发送一个硬中断,或者受到一个信号都可以唤醒进程进入TASK_RUNNING状态。
不可中断等待状态
与可中断等待的唯一区别就是不能够被信号唤醒。通常用于特殊情况,例如打开设备文件时,相应的设备驱动程序开始探测相应的硬件设备,这个过程中,驱动程序是不能够被中断的。
暂停状态
进程暂停执行
跟踪状态
进程的执行被调试器暂停
僵死状态
表示进程执行被终止,但是其夫进程还未调用wait4或waitpid系统调用来返回有关终止进程的信息。
僵死撤销状态
表示进程的最终状态,父进程已经调用wait4或者waitpid
不可交互等待状态
进程处于等待状态并且无论它时是否可以交互都不提供任何信息。
死亡状态
特殊进程
进程0与进程1是两个特殊进程,进程0是idle进程,当没有其他进程处于TASK_RUNNING状态时,该进程就会被执行。CPU的空闲时间其实就是这个idle进程的运行时间。进程1则是init进程,init进程是用户空间的首个进程,用于孵化其他进程。
进程的内核栈
进程是不断变化的实体,内核为每个进程都分配了一个固定大小的内核栈,用来保存进程在内核态的函数调用信息以及进程描述符。
获取进程描述符
进程描述符可以从内核栈中获取,系统已经定义好了宏用来获取进程描述符。
进程创建
基本步骤:
- 查找可执行程序
- fork自身
- 子进程执行
exec()
装入该程序
fork()
能够让进程复制一个与自身完全一样的进程,exec()
则会读取一个外部程序来代替自身。
fork()
fork()
会创建一个与自身完全一样的进程,包括各种资源。因此如果不优化fork的话,每次创建进程都会花费很大的开销。
因此,Linux内核对fork进行了优化,使之不完全复制父进程的资源,只复制页表,而且采取了写时复制的策略,让子进程在修改资源的时候复制一份副本,修改之后再回写,这其实跟Java的多线程内存共享机制是一样的。
vfork()
vfork()
其实跟优化之后的fork()
差不多,只不过vfork()
连页表也不会复制。
clone()
clone()
与fork()
最大的不同是,clone()
可以选择性地复制父进程的资源,甚至可以让旧进程与新进程不再是父子关系,而是兄弟关系。
内核线程
内核线程就是运行在内核态的线程,普通的线程进入到内核态需要经过系统调用,但是内核线程不需要。