1.什么是进程
进程就是处于执行期的程序,但是进程不仅仅局限于一段可执行的程序代码,通常还包括其它的资源,像打开的文件,处理器的状态,地址空间等。简而言之,进程=运行中的程序+相关资源
如果说进程是操作系统的最小资源分配单位,那么线程就是最小执行单位。内核调度的对象是线程而不是进程,线程有自己独立的程序计数器,进程栈和一组进程寄存器。
2.进程描述符及任务队列
进程描述符——pcb,在linux中存放在一个叫任务队列的双向链表中,以task_struct结构体的形式存在,大小约1.7kb,通过slab分配器分配内存。在内核中访问任务通常需要获得指向其task_struct结构的指针,linux通过在内核栈的尾端创建thread_info结构,通过计算偏移间接的查找task_struct结构体。
进程的家族树:进程描述符存储在任务队列中,是双向链表的形式。但是tast_struct中有一个parent指针和一个叫做children的子进程指针链表,这样构成了一个类似复杂链表的形式。
3.进程的状态
进程有五种状态
TASK_RUNNING:运行态,进程在等待执行或者正在执行。
TASK_INTERRUPTIBLE:可中断,进程在阻塞状态并可被特定条件唤醒。也可被信号提前唤醒。
TASK_UNINTERRUPTIBLE:不可中断。与可中断状态相似,不同的是不可被信号唤醒。
__TASK_TRACED:被其它进程跟踪的进程。
__TASK_STOPPED:停止状态。
4.进程的创建
1.写时拷贝
进程的产生过程:首先在新的地址空间里创建进程,然后读入可执行文件,最后开始执行。
linux使用fork()和exec()两个函数来执行上述步骤,首先fork()拷贝当前进程创建一个子进程,子进程与父进程的区别于PID和PPID以及某些资源和统计量。exec()函数则负责读取可执行文件并将其载入地址空间开始运行。
子进程不是总会使用父进程中的数据,如果fork之后子进程立刻exec,那么对数据的拷贝就完全时多余的,因此linux采用了写时拷贝的技术,当父进程或子进程需要改变时才会拷贝。这样,fork()的实际开销就是复制了父进程的页表以及给子进程创建唯一的文件描述符。
2.fork函数和vfork函数
linux通过clone系统调用实现fork函数,这个调用通过一系列的参数来标志来指明父子进程需要共享的资源,因此vfork函数以及线程的创建函数都是通过调用clone函数,clone函数调用do_fork来实现。
fork函数会调用clone函数,然后clone调用do_fork,do_fork完成了创建中的大部分工作,该函数调用copy_process函数,然后让子进程开始运行。copy_process调用dup_task_struct()为新进程创建一个内核栈,thread_info结构和task_struct,这时父进程和子进程描述符相同。然后子进程会修改状态,更新flags,重新分配PID,根据传递clone的参数拷贝相应的资源。
vfork函数除了不拷贝父进程的页表外,vfork系统调用和fork的功能相同,但是子进程会作为父进程的一个单独的线程在它的地址空间里运行,父进程被阻塞,直到子进程退出或执行exec函数。在没有写时复制的时候,vfork相对于fork有比较好的优化,但是当引入写时复制之后,vfork的作用仅仅是不用复制页表项。
线程在其它操作系统中是一种轻量级的进程,但是在linux中,由于进程的实现已经非常轻量,因此对于线 程的实现与进程非常类似,在linux内核看来,它就是一个普通的进程。因此,可以将线程看作是一种共享资源手段。