进程不但包括程序的指令和数据,而且包括程序计数器和处理器的所有寄存器及存储临时数据的进程堆栈,因此正在执行的进程包括处理器当前的一切活动。
因为Linux是一个多进程的操作系统,所以其他的进程必须等到系统将处理器使用权分配给自己之后才能运行。当正在运行的进程等待其他的系统资源时,Linux内核将取得处理器的控制权,并将处理器分配给其他正在等待的进程,它按照内核中的调度算法决定将处理器分配给哪一个进程。
内核将所有进程存放在双向循环链表(进程链表)中,其中链表的头是init_task描述符。链表的每一项都是类型为task_struct,称为进程描述符的结构,该结构包含了与一个进程相关的所有信息,定义在<include/linux/sched.h>文件中。task_struct内核结构比较大,它能完整地描述一个进程,如进程的状态、进程的基本信息、进程标识符、内存相关信息、父进程相关信息、与进程相关的终端信息、当前工作目录、打开的文件信息、所接收的信号信息等。
下面详细讲解task_struct结构中最为重要的两个域:state(进程状态)和pid(进程标识符)。
1)进程状态
Linux中的进程有以下几种状态。
● 运行状态(TASK_RUNNING):进程当前正在运行,或者正在运行队列中等待调度。
● 可中断的阻塞状态(TASK_INTERUPTIBLE):进程处于阻塞(睡眠)状态,正在等待某些事件发生或能够占用某些资源。处在这种状态下的进程可以被信号中断。接收到信号或被显式的唤醒呼叫(如调用wake_up系列宏:wake_up、wake_up_interruptible等)唤醒之后,进程将转变为TASK_RUNNING 状态。
● 不可中断的阻塞状态(TASK_UNINTERUPTIBLE):此进程状态类似于可中断的阻塞状态(TASK_INTERRUPTIBLE),只是它不会处理信号,把信号传递到这种状态下的进程不能改变它的状态。在一些特定的情况下(进程必须等待,直到某些不能被中断的事件发生),这种状态是很有用的。只有在它所等待的事件发生时,进程才被显式的唤醒呼叫唤醒。
● 可终止的阻塞状态(TASK_KILLABLE):Linux内核 2.6.25 引入了一种新的进程状态,名为 TASK_KILLABLE。该状态的运行机制类似于 TASK_UNINTERRUPTIBLE,只不过处在该状态下的进程可以响应致命信号。它可以替代有效但可能无法终止的不可中断的阻塞状态(TASK_UNINTERRUPTIBLE),以及易于唤醒但安全性欠佳的可中断的阻塞状态(TASK_INTERRUPTIBLE)。
● 暂停状态(TASK_STOPPED):进程的执行被暂停,当进程收到SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU等信号时,就会进入暂停状态。
● 跟踪状态(TASK_TRACED):进程的执行被调试器暂停。当一个进程被另一个进程监控时(如调试器使用ptrace()系统调用监控测试程序),任何信号都可以把这个进程置于跟踪状态。
● 僵尸状态(EXIT_ZOMBIE):进程运行结束,父进程尚未使用wait函数族(如使用waitpid()函数)等系统调用来“收尸”,即等待父进程销毁它。处在该状态下的进程“尸体”已经放弃了几乎所有的内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集。
● 僵尸撤销状态(EXIT_DEAD):这是最终状态,父进程调用wait函数族“收尸”后,进程彻底由系统删除。
它们之间的转换关系如图3.2所示。
图3.2 进程状态转换关系图
内核可以使用set_task_state和set_current_state宏来改变指定进程的状态和当前执行进程的状态。
2)进程标识符
Linux内核通过唯一的进程标识符PID来标识每个进程。PID存放在进程描述符的pid字段中,新创建的PID通常是前一个进程的PID加1,不过PID的值有上限(最大值 = PID_MAX_DEFAULT – 1,通常为32767),读者可以查看/proc/sys/kernel/pid_max来确定该系统的进程数上限。
当系统启动后,内核通常作为某一个进程的代表。一个指向task_struct的宏current用来记录正在运行的进程。current经常作为进程描述符结构指针的形式出现在内核代码中,例如,current->pid表示处理器正在执行的进程的PID。当系统需要查看所有的进程时,则调用for_each_process()宏,这将比系统搜索数组的速度要快得多。
在Linux中获得当前进程的进程号(PID)和父进程号(PPID)的系统调用函数分别为getpid()和getppid()。
本文选自华清远见嵌入式培训教材《从实践中学嵌入式Linux应用程序开发》 |