《linux内核设计与实现》第三章读书笔记--进程相关概念

博主在看cpu和内存调优的时候遇到一些底层的内核概念不甚了解,所以阅读了《linux内核设计与实现》一书,并对内核相关概念做个简练的总结,以便大家在对系统调优时有更好地理解。
1.进程定义
进程是程序的子集,线程是进程的子集。一个进程由一个或多个线程组成,内核调度对象的最小单位就是线程。linux系统的线程实例非常特别:它对线程和进程并不特别区分,线程是一种特殊的进程。
操作系统中,进程提供两种虚拟机制,虚拟处理器和虚拟内存。虚拟处理器给进程一种假象:让进程觉得自己独享处理器,实际上可能是许多进程正在分享一个处理器。而虚拟内存让进程在分配时觉得自己拥有整个系统的所有内存资源。
程序本身不是进程,进程是处于执行期的程序以及相关的资源总称,可能两个或多个不同的进程执行的是同一个程序。
fork()函数通过复制一个现有进程还创建一个全新的进程,调用fork()的进程称为父进程,新产生的进程称为子进程。接着调用exec()这组函数创建新的地址空间。最后程序通过exit()系统函数调用退出执行。这个函数会终结进程并将其占用的资源释放掉。父进程可以通过wait4()系统函数调用来查询子进程是否终结。进程退出执行后被设置为僵死状态,知道他的父进程调用wait()或waitpid()为止。
2.进程描述符及任务结构
内核把进程的列表存放在叫做任务队列(task list)的双向循环链表中,链表中每一个项都是类型为task_struct、称为进程描述符的结构。在32位机器上,它大约有1.7KB。包含的数据哼完整地描述一个正在执行的程序:它的打开文件、进程的地址空间、挂起的信号、进程的状态等等。
当我们使用一些检查CPU状况的指令时可以看到队列长度,比如sar -q


内核通过一个唯一的进程标识值(PID)来标识每一个进程,PID的最大值默认设置为32768(它在slab分配器创建task_struct的时候,是以short int短整型定义的,所以这个值是短整型的最大值),内核定义的最大PID是400万。内核把每个进程的PID存放在他们各自的进程描述符中。可以修改/proc/sys/kernel/pid_max来提高上限。
3.进程状态
在内核中,访问进程通常需要获得指向其task_struct的指针。大部分处理进程的代码都是直接通过task_struct进行的。有的硬件体系结构可以拿出一个专门的寄存器来存放指向当前进程task_struct的指针,用于加快访问速度。
进程描述符中的state描述了进程当前的状态,共五种:
TASK_RUNNING:可执行,正在执行或等待执行。
TASK_INTERRUPTIBLE:可中断,正在睡眠,或者说被阻塞了。
TASK_UNINTERRUPTIBLE:不可中断。就算是接收到信号也不会响应。用的较少。
__TASK_TRACED:被其他进程跟踪的进程,例如通过ptrace对调试程序进行跟踪。
__TASK_STOPPED:进程停止执行,进程没有投入运行也不能投入运行。通常这种状态发生在接收到中断信号时,比如SIGSTOP、SIGSTP、SIGTTIN等。
其实这五种状态在ps的手册里也可以找到,执行man ps

其中D状态就是不可中断,这样的任务由于不响应信号,是杀不死的(SIGKILL)。遇上这样的进程时最好也别杀死他,因为该进程有可能在执行重要的操作。
看到这里,结合进程状态和进程的定义,我们来看下衡量CPU重要指标平均负载(load average)是怎么定义的:
在执行man sar指令后可以找到:the load average is calculated as the average number of runnable or running tasks(R state), and the number of tasks in uninterruptible sleep(D state) over the specified interval. 翻译过来就是:在特殊时间间隙内,处于可运行状态或正在运行、和不可中断的进程平均数。又因cpu核数就是跑的进程数(未开启超线程模式),现在可以理解为什么平均负载不要长期大于2了吧。(这里不特定区分进程和线程)

4.继承关系
类Unix系统中进程之间存在继承关系。所有进程都是PID为1的init进程的后代,内核在系统启动的最后阶段启动init进程。该进程读取系统的初始化脚本并执行其他相关程序,最终完成系统的启动。进程间的关系存放在进程描述符中,每个task_struct都包含一个指向其父进程的parent指针,还包含一个称为children的子进程链表。
线程在同一个程序内共享内存地址空间,以及打开的文件和其他资源。在多处理器上能实现并行处理。linux内核是把线程当做进程来实现的。内核没有准备特别的调度算法或数据结构来表征线程。每个线程也有task_struct, 结构上没有区别,只是和某些线程共享某些资源。
进程退出时,给父进程发送信号,给子进程重新找养父,养父为线程组中的其他线程或者为init进程,并把进程状态设置为EXIT_ZOMBIE(图1里可以找到此状态),该状态下进程不再被调度,这是进程所执行的最后一段代码。至此与进程相关的所有资源被释放。但是它还是占有一点内存的,包括内核栈、thread_info结构和tast_struct结构,此时它向父进程提供信息,父进程检索到后,释放剩余内存。
如果父进程在退出之前没有给子进程找到养父,这些孤儿进程机会在退出时永远处于僵死状态。解决方法就是在当前线程组内找一个线程作为父亲,或者让init做父进程。在fork ()/execve()过程中,假设子进程结束时父进程仍存在,而父进程fork()之前既没安装SIGCHLD信号处理函数调用waitpid()等待子进程结束,又没有显式忽略该信号,则子进程成为 僵尸进程,无法正常结束,此时即使是root身份kill -9也不能杀死 僵尸进程。补救办法是杀死僵尸进程的父进程(僵尸进程的父进程必然存在),僵尸进程成为"孤儿进程",过继给1号进程init,init始终会负责清理僵尸进程。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值