闲话Linux进程
进程是操作系统运行的基础,本文以比较粗犷的角度闲侃linux进程方面的知识。
用户态的每个进程在内核中对应一个task_struct结构的进程描述符,描述符中包含进程的状态和执行信息、虚拟内存信息、打开文件信息、以及文件文件系统信息,信号相关信息等(进程使用的资源时有限制的,如打开文件数,使用的物理页,文件大小等,其值包含在描述符中task_struct->signal->rlim字段中,也可通过ulimit –a进行查看)。
每个进程由一个ID来标示,称为进程号,内核通过位图的管理方式来为新创建的进程分配进程号。内核将所有进程描述符使用链表链接起来以进行统一管理(可通过ps命令查看进程的信息),进程链表的头式init_task描述符,即0进程或称swapper进程的描述符,同时为了方便通过进程号获取进程描述符,内核将所有的进程按照其PID(PGID,SID)链入到不同散列表。
进程可通过fork系统调用进行创建,新创建的进程成为子进程,在其创建时,其复制了其父进程的所有资源,是父进程的副本。execve系统调用可以执行新的应用程序,通常fork与execve系统调用配合起来使用,通过fork创建子进程,然后在子进程中调用execve执行新的应用程序(用应用程序的虚拟地址空间替换子进程的虚拟地址空间)。
所有进程的祖先叫做进程0,idle进程或swapper进程,它是在linux的初始化阶段从无到有创建的一个内核线程,该进程使用了一系列的静态分配的数据结构进行初始化(INIT_MM, INIT_FS等)。进程0执行cpu_idle函数,本质上是在开中断的情况下重复执行hlt指令,只有当没有其他进程处于可运行状态时,调度程序才选择进程0。进程0初始化内核需要的所有数据结构后,创建一个叫进程1的内核线程(init进程),通过execve系统调用装入/sbin/init执行,在系统关闭之前,init进程一直存活,因为它创建和监控在操作系统外层执行的所有进程的活动。
为了方便调度,内核为进程设置了多种状态,可运行状态,可中断等待状态,不可中断等待状态,暂停状态等,根据不同的条件,进程可以在不同的状态下进行转换。另外考虑到任务性质不同,进程具有不同的优先级,每个可运行状态的进程描述符同时被链接到相应的优先级链表中(优先级链表共140个,对应的优先级从0-139)。而对于处于等待状态的进程,其被划分为若干的类别,每个类别代表一个特定的事件,并通过等待队列来组织进程。
调度器的主要工作包括选择下一个要运行的进程并进行上下文切换(与体系结构相关的操作,由switch_to完成)。内核的调度程序主要由schedule函数完成,schedule()函数可以通过两种方式调用:
1. 主动调度,直接调用函数schedule(),如进程退出,或者进入睡眠状态等。
2. 强制性调度。置位当前进程task_struct里面的need_resched。当是从内核态返回用户态的时候将检查这个位,如果发现已经被置位,会调用schedule(),和主动调度相比,强制性调度有一定的调度延时;有以下三种情况可能会置位need_resched:
a. 时钟中断服务程序中,发现进程已经用完自己的时间片,需要被切出CPU;
b. 当唤醒一个睡眠进程时,发现该进程比当前占有CPU的进程优先级高;
c. 一个进程通过系统调用改变调度策略、优先级等。