进程与线程
程序是存在磁盘上的可执行文件;
进程是程序的执行实例,是资源(CPU时间、内存等)分配和拥有的实体。
进程ID:进程的唯一数字标识符,用pid_t定义,用getpid()得到,需要头文件unistd.h。PID最大32767。
进程描述符process descriptor:都是task_struct类型结构,它的字段包含了与一个进程相关的所有信息(属性、指针)。
用于进程控制的主要函数:fork、exec、waitpid。
创建进程时,接受父进程地址空间等一个(逻辑)拷贝,并从进程创建系统调用的下一条指令开始执行与父进程相同的代码。
waitpid()会暂时停止目前进程的执行,直到有信号来到或子进程结束。需要头文件sys/types和sys/wait.h
线程:是CPU调度的最小单位。通常一个进程只有一个控制线程thread。在一个进程内的所有线程共享同一地址空间、文件描述符、栈以及与进程相关的属性。
一个进程有几个用户线程组成,每个线程都代表进程的一个执行流。(pthread库的标准库函数集编写)
进程状态
进程描述符中state字段描述了进程当前所处的状态。用标志表示进程状态,它们是互斥的。
可运行状态 TASK_RUNNING(进程中CPU上执行或准备执行)
可中断的等待状态 TASK_INTERRUPTIBLE(进程被挂起即睡眠,直到某个条件变为真。硬件中断,释放进程正等待的系统资源;或传递一个信号可唤醒进程,进程状态则放回到TASK_RUNNING。)
不可中断的等待状态 TASK_UNINTERRUPTIBLE(信号传递到睡眠进程不能改变它的状态)
暂停状态 TASK_STOPPED(进程的执行被暂停。当进程接收到SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU信号后,进入暂停状态。)
跟踪状态 TASK_TRACED(进程的执行已由debugger程序暂停)
僵死状态 EXIT_ZOMBIE(进程的执行被终止,但父进程没有发布wait4()或waitpid()系统调用来返回有关死亡进程的信息)
僵死撤销状态 EXIT_DEAD(最终状态:由于父进程刚发出wait4()或waitpid()系统调用,因而由系统删除。)
进程最常用的是进程描述符的地址,而不是thread_info结构的地址。为了获得当前在CPU上运行进程的描述符指针,内核要调用current宏,本质上等价于current_thread_info()->task;current->pid返回在CPU上正在执行的进程的PID。
进程链表:是双向链表,把所有进程的描述符链接起来。每个task_struct结构都包含一个list_head类型的tasks字段,这个类型的prev和next字段分别指向前面和后面的task_struct元素。
进程链表的头是init_task描述符,它是0进程或swapper进程的进程描述符。init_task的tasks.prev字段指向链表中最后插入的进程描述符的tasks字段。
SET_LINKS和REMOVE_LINKS宏分别用于从进程链表中插入和删除一个进程描述符。for_each_process功能是扫描整个进程链表。
进程间的关系
程序创建的进程具有父子关系。进程0和进程1是由内核创建,进程1(init)是所有进程的祖先。
0号进程->1号内核进程->1号用户进程(init进程)->getty进程->shell进程
init()函数在内核态运行,是内核代码;
init进程是内核启动并运行的第一个用户进程,运行在用户态下。
进程切换
为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种行为叫进程切换process switc、任务切换或上下文切换。
进程恢复执行前必须装入寄存器的一组数据称为硬件上下文。硬件上下文一部分存在TSS段(任务状态段Task State Segment)。
进程切换只发生在内核态,之前,用户态进程使用的所有寄存器内容都已保存在内核态堆栈上。
进程切换用schedule()函数,分两步:切换页全局目录以安装一个新的地址空间;切换内核态堆栈和硬件上下文。
prev和next是schedule()函数的局部变量,prev指向被替换进程的描述符,next指向被激活进程的描述符。
创建进程
用户输入一条命令,shell进程就创建了一个新进程,新进程执行shell的另一个拷贝。
子进程复制父进程的所有资源。实际上子进程几乎不必读或修改父进程拥有的所有资源,在很多情况下,子进程立即调用execve()。
所有的进程切换都由宏switch_to来完成。
Linux把进程分为交互式进程(shell、文本编辑程序及图形应用程序)、批处理进程(常在后台运行)、实时进程(视频、音频、传感器程序等)。