1、进程
进程指的是处于执行期的程序,同时包括其他资源,像打开的文件,挂起的信号,内核内部数据,处理器状态,一个或多个具有内存映射的内存地址空间及一个或多个执行线程,当然还包括用来存放全局变量的数据段等。
线程:是在进程中活动的对象。Linux系统的线程实现非常特别:它对线程和进程并不特别区分,对Linux而言,线程只不过是一种特殊的进程罢了。
现代操作系统中,提供两种虚拟机制:虚拟处理器和虚拟内存。两者给进程一种假象,让这些进程觉得自己在独享处理器,在分配和管理内存是觉得自己拥有整个系统的所有内存资源。
2、进程描述符及任务结构
内核把进程的列表存放在叫做任务队列的双向循环链表中。链表中每一项都是类型为task_struct、称为进程描述符的结构。
内核通过一个唯一的进程标识值或PID来标识每个进程。PID的最大值默认设置为32768。内核把每个进程的PID存放在它们各自的进程描述符中。
3、进程状态
进程描述符中的state域描述了进程的当前状态。系统中每个进程都必然处于五种进程状态中的一种。
- TASK_RUNNING(运行)——进程是可执行的:它或者正在执行,或者在运行队列中等待运行。
- TASK_INTERRUPTIBLE(可中断)——进程正在睡眠,等待某些条件的达成。
- TASK_UNINTERRUPTIBLE(不可中断)
- __TASK_TRACED——被其他进程跟踪的进程
__TASK_STOPPED(停止)——进程停职执行
设置当前进程状态:set_task_state(task,state) /*将任务task的状态设置为state*/
进程家族树:所有的进程都是PID为1的init进程的后代。拥有同一个父进程的所有进程称为兄弟。
4、进程创建
Linux采用了与众不同的实现方式,使用两个单独的函数去执行创建线程工作:fork()和exec().
fork:通过拷贝当前进程创建一个子进程。
exec:负责读取可执行文件并将其载入地址空间开始运行。
fork()-->clone()-->do_fork()-->copy_process()
copy_process()完成大部分工作:
- 调用dup_task_struct()为新进程创建一个内核栈、thread_info结构和task_struct,其内容和父进程相同;
- 检查新进程是否超过进程数目最大限制;
- 清理新进程的信息(比如PID置0等),使之与父进程区别开;
- 设置进程状态为TASK_UNINTERRUPTIBLE;
- 调用copy_flags()更新task_struct的flags成员;
- 调用alloc_pid()分配有效PID;
- 根据clone()的参数标志,拷贝或共享相应的信息;
- 最后,做扫尾工作并返回一个指向子进程的指针。
在Linux中,线程仅仅被视为一个与其他进程共享某些资源的进程。
线程的创建与进程类似,只不过在调用clone()的时候需要传递一些参数标志来指明需要共享的资源。
5、线程终结
和创建过程类似,终结过程也有一些烦琐的工作:
- 将task_struct的标志成员设置成PF_EXITING;
- 调用del_timer_sync()删除内核定时器, 确保没有定时器在排队和运行;
- 调用acct_update_integrals()来输出记账信息;
- 调用sem__exit(),使进程离开等待IPC信号的队列;
- 调用exit_files()和exit_fs(),以分别递减文件描述符、文件系统数据的引用计数。
- 把存放在task_struct的exit_code成员中的任务退出代码置为又exit()提供的退出代码;
- 调用exit_notify()给子进程重新找父进程,且进程状态设为EXIT_ZOMBIE;
- 调用schedule()切换到新的进程执行。
子进程进入EXIT_ZOMBIE之后,虽然永远不会被调度,关联的资源也释放掉了,但是它本身占用的内存还没有释放,比如创建时分配的内核栈,task_struct结构等。这些由父进程来释放。