描述
开 本: 16开纸 张: 胶版纸包 装: 平装-胶订是否套装: 否国际标准书号ISBN: 9787302489733
目录
第1章Linux安装与访问1
1.1安装Linux1
1.1.1下载CentOS 71
1.1.2CentOS 7安装3
1.2虚拟机安装Linux9
1.2.1VirtualBox安装9
1.2.2虚拟机配置17
1.2.3虚拟机安装Linux19
1.3ssh远程终端访问21
1.4初次接触Linux24
1.4.1简单操作24
1.4.2运行HelloWorld程序29
1.4.3操作系统接口32
1.5阅读注意事项33
1.6本章小结34
第2章进程控制35
2.1进程基本概念35
2.1.1进程实体37
2.1.2进程间组织关系38操作系统之编程观察目录2.1.3进程控制命令42
2.2创建与撤销进程44
2.2.1fork()创建子进程44
2.2.2孤儿进程和僵尸进程47
2.2.3exec函数族49
2.2.4通过kill()撤销进程51
2.2.5创建守护进程51
2.3创建pthread线程56
2.3.1进程与线程57
2.3.2创建方法58
2.4进程和线程资源开销60
2.4.1PCB开销60
2.4.2内存描述符开销63
2.5本章小结64
2.6练习65
第3章进程调度66
3.1调度与均衡66
3.1.1调度与均衡框架71
3.1.2全系统的调度统计72
3.2进程状态及其转变75
3.2.1进程状态75
3.2.2状态转换80
3.2.3进程的调度统计83
3.3进程的调度88
3.3.1普通进程的CFS调度88
3.3.2实时进程调度93
3.4进程迁移与负载均衡103
3.4.1CFS进程的负载均衡104
3.4.2实时进程的负载均衡111
3.5本章小结114
3.6练习114
第4章进程间通信与同步115
4.1进程间通信115
4.1.1管道115
4.1.2System V IPC120
4.2进程间同步133
4.2.1System V IPC信号量集133
4.2.2POSIX信号量145
4.3本章小结154
4.4练习154
第5章内存管理156
5.1虚存空间管理156
5.1.1进程映像156
5.1.2堆区161
5.1.3文件映射区168
5.1.4栈区171
5.1.5访问任意进程的虚存176
5.1.6虚存使用的物理页帧179
5.2分页机制与页表181
5.2.1分页机制182
5.2.2进程页表186
5.3物理内存组织管理193
5.3.1页帧、节点、内存域193
5.3.2空闲页帧管理——buddy系统200
5.3.3物理内存分配与回收204
5.3.4内存回收215
5.4本章小结227
5.5练习228
第6章综合——新进程创建到运行229
6.1shell读入命令229
6.1.1用户空间与内核空间229
6.1.2读入命令231
6.2创建进程237
6.2.1fork()复制进程237
6.2.2替换进程映像238
6.2.3开始运行新进程239
6.2.4进程映像与缺页240
6.3本章小结240
6.4练习241
第7章VFS文件系统242
7.1VFS242
7.1.1VFS对象243
7.1.2文件系统类型245
7.2文件基本操作246
7.2.1命令行基本操作246
7.2.2编程接口248
7.3目录结构253
7.3.1树形结构253
7.3.2软/硬链接254
7.3.3文件系统创建与安装258
7.4页缓存264
7.4.1页缓存基本概念265
7.4.2页缓存动态变化266
7.5非文件功能268
7.5.1交换268
7.5.2设备接口273
7.5.3proc文件系统279
7.6本章小结280
7.7练习280
第8章EXT2文件系统281
8.1EXT2磁盘数据的组织281
8.1.1整体布局281
8.1.2超级块283
8.1.3块组描述符285
8.1.4索引节点286
8.1.5目录结构289
8.2EXT2文件系统的创建291
8.2.1分配磁盘空间291
8.2.2创建环回设备292
8.2.3创建EXT2文件系统293
8.2.4安装文件系统293
8.3查看EXT2磁盘数据294
8.3.1布局信息294
8.3.2块组描述符299
8.3.3索引节点与文件内容300
8.3.4目录结构304
8.4本章小结309
8.5练习310
附录vi编辑命令311
序
深圳大学计算机与软件学院正在进行教学改革,基于明仲教授和王志强教授两位领导的构想,计算机与软件学院参照美国纽约宾汉姆敦大学教学要求,将其课程在深圳大学原样重现。对计算机系统系列课程和操作系统课程都进行了改革,其中操作系统课程缩减了理论授课,增加了实验操作环节。同时深圳大学计算机与软件学院计算机系统课程组正在承担广东省教育厅应用型人才培养项目(计算机系统系列核心课课程),强调丰富的动手实践经验并提高系统能力、系统思维。在上述环境下,为了充实实验内容,保持学生在课程学习中全程充实,我们完成了本书以配合理论教学。
在过去的操作系统教学过程中,作者深感理论教学与实践的脱节之困。虽然也有老师讲授Linux工程实践和系统编程,从而拉近了两者的距离,但都还未能与操作系统的核心概念紧密联系,仍似隔靴搔痒,可望而不可即。另外也考虑过将Linux内核源码分析或增强作为实践内容,但是由于学习曲线过于陡峭需要花费太多的时间,并不太适合作为课程内容——难以在一个学期课程中结合进来,甚至还可能让学生产生无功而返的挫败感。
根据我们在个人高性能计算机(PHPC)系统研制过程中对研究生培养的经验积累,将Linux系统编程的基础知识结合Linux的内核行为观察,利用/proc文件系统中探测到的内核数据,以及其他各种工具收集的内核数据,直观生动地将进程与内核的交互、内核的行为展示给学生,获得非常好的学习效果。学生对进程行为、内存分配管理、进程间通信和文件系统等各方面的认知,都远比传统的操作系统课程教学效果好。
读者在学习和体验操作系统各种概念的同时,也获得了初步的系统编程实践锻炼,并为进一步阅读Linux内核源代码做好了充足的准备。将proc文件系统和相关工具加入到操作系统的学习过程中,相当于有了电路系统课程中的“万用表、示波器和逻辑分析仪”等工具,有了观测工具后,操作系统的教学和实验才算基本成熟了。正因为这些观测工具,使得一些读者在完成全书学习后可能会觉得:
“哦,原来这才是操作系统!”经过这样的实践锻炼后,读者不仅可以在后续学习中加快系统编程的学习进度,还可以加快获得分析和修改Linux内核代码能力的培养进程。
编者
2018年4月操作系统之编程观察
致谢
本书获得深圳市科创委基础研究JCYJ20150930105133185项目和JCYJ20170302153920897云环境中的异构存储资源分配与性能优化研究的资助。感谢深圳大学计算机与软件学院操作系统课程组的各位老师,大家一起完成了操作系统实验课程的改革,特别是张滇和周明洋两位老师在相关实验内容的检查和教学工作中做出了极大的贡献。
还需要感谢2014级几位同学在相关的材料整理和实验代码的设计中做出的贡献。其中林润胜同学完成了第5章的匿名映射、文件映射、meminfo和zoneinfo解读及相关代码,第8章的EXT2文件系统中文件内容读取、目录读取及相关代码,协助完成了第6章的勘误;张永昌同学完成了5.1.1节、7.3.3节的内容和相应的代码。这两位同学一起提供了5.3.3节的部分内容,完成了第5章内存管理和第8章EXT2文件系统的勘误工作。罗文杰同学完成了4.2.1节的信号量集相关代码和材料。2017级研究生汤钊扬同学作为第一位读者,协助验证代码和完成勘误工作。
在上述老师和同学的大力支持下,本书终于完稿并与读者见面,再次对他们表示衷心的感谢!
fpu_exception: yescpuid level : 13wp : yesflags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc pni pclmulqdq ssse3 cx16 sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx rdrand hypervisor lahf_lm abmbogomips : 7183.36clflush size : 64cache_alignment : 64address sizes : 39 bits physical, 48 bits virtualpower management:
processor : 1vendor_id : GenuineIntelcpu family : 6model : 60model name : Intel(R) Core(TM) i7-4790 CPU @ 3.60GHzstepping : 3cpu MHz : 3591.684cache size : 8192 KBphysical id : 0 siblings : 2core id : 1cpu cores : 2apicid : 1initial apicid : 1fpu : yesfpu_exception : yescpuid level : 13wp : yesflags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc pni pclmulqdq ssse3 cx16 sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx rdrand hypervisor lahf_lm abmbogomips : 7183.36clflush size : 64
cache_alignment: 64address sizes : 39 bits physical, 48 bits virtualpower management:将虚拟机配置为多核系统并启动后,top命令也会有些不同(按“1”键可以查看各个核的使用情况信息)。例如top命令查看系统中按CPU使用率排序的进程列表时,通常如屏显32所示,前 5行是系统整体的统计信息,后面是各进程的统计信息。从第一行可以看出,该系统已经启动运行了12分钟,有3个用户登录(同一个账号多次登录记为多个) ,1/5/15分钟内的平均负载load average分别为1.00/0.91/0.56。第二行表明任务总数Tasks为178,就绪running状态的进程3个,175个进程处于阻塞状态sleeping,处于暂停或被跟踪stopped状态的进程数为0,处于僵尸zombie状态的进程数为0。以“%Cpu(s)”标志开头的第三行是CPU使用情况的统计,默认是将所有核的利用率一起统计。其中53.1 us表示53.1%的时间用于运行用户空间的代码,sy表示运行内核代码占用CPU时间的百分比,ni表示低优先级用户态代码占用CPU时间的百分比,id表示空闲(运行idle任务进程)CPU时间的百分比,wa表示IO等待占用CPU时间的百分比,hi表示硬件中断(Hardware IRQ)占用CPU时间的百分比,si表示软件中断(software interrupts)占用CPU时间的百分比以及和虚拟化有关的st(steal time)占用CPU时间的百分比。屏显32top命令的输出top – 21:48:40 up 12 min, 3 users, load average: 1.00, 0.91, 0.56Tasks: 178 total, 3 running, 175 sleeping, 0 stopped, 0 zombie%Cpu(s): 53.1 us, 0.3 sy, 0.0 ni, 46.6 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 stKiB Mem: 1883672 total, 858304 free, 517724 used, 507644 buff/cacheKiB Swap: 1679356 total, 1679356 free, 0 used. 1170184 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND 3866lqm 200 4160 344 272R100.00.010:31.61HelloWorld 3088lqm200175018020392449232S3.010.80:08.87gnome-shell1188root2002583083962010156R2.32.10:03.42Xorg3720lqm2005750762464014428S0.71.30:01.13gnome-term 646root2004368592496S0.30.00:01.21rngd2815lqm200358602204948S0.30.10:00.17dbus-daemon3046lqm20010941682418416112S0.31.30:00.32gnome-sett
3252lqm2008809562778417612S0.31.50:00.30nautilus3869lqm20015770822681564R0.30.10:00.66top1root20019363267243964S0.00.40:01.88systemd2root200000S0.00.00:00.00kthreadd3root200000S0.00.00:00.01ksoftirqd/05root0-20000S0.00.00:00.00kworker/0: 6root200000S0.00.00:00.13kworker/u4 7rootrt0000S0.00.00:00.00migration/08root200000S0.00.00:00.00rcu_bh第4行和第5行是内存相关的统计,其内容和free命令的输出完全相同,请参见屏显334及相关说明。屏显32第6行及以下是各个进程的统计信息: PR和NI是优先级和NICE值,S是进程的状态,它们与调度有关;%CPU是该进程占用单个CPU时间的百分比;TIME 是进程在CPU上运行的时间(不计阻塞时间)。VIRT、RES和SHR与调度没有直接关系,它们分别是进程虚存空间大小、物理内存占用量和共享物理内存大小(按千字节统计),%MEM指该进程占系统物理内存总量的百分比。在屏显32上可以看到,进程3866占有单个处理器核的100%利用率(占用了双核系统的一半CPU资源),且进程状态为R(就绪/运行)。同为R状态的还有1188和3869号进程,其他进程都处于S阻塞状态。 如果按数字“1”键将会分别显示各处理器的统计信息,可以不用查看/proc/cpuinfo也获得处理器个数的信息。例如在系统上执行top命令并按“1”键,分别显示CPU0和CPU1各自的统计信息,如屏显33所示。屏显33top命令的输出(展开CPU利用率)top – 22:20:05 up 44 min, 3 users, load average: 0.92, 0.96, 0.93Tasks: 179 total, 3 running, 176 sleeping, 0 stopped, 0 zombie%Cpu0 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st%Cpu1: 18.5 us, 2.0 sy, 0.0 ni, 79.5 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 stKiB Mem : 1883672 total, 846520 free, 529288 used, 507864 buff/cacheKiB Swap: 1679356 total, 1679356 free, 0 used. 1158508 avail Mem
PIDUSERPRNIVIRTRESSHRS%CPU%MEMTIME COMMAND 3866lqm2004160344272R99.70.041:54.92HelloWorld 3088lqm200175587621022449360S12.911.20:18.42gnome-shell……读者准备好多核系统后,下面进入调度与均衡的讨论。3.1.1调度与均衡框架就绪进程在Linux内核中的组织分成若干层次,首先是按各处理器(逻辑CPU)组织成不同的运行队列rq(run queue),在每个处理器的rq内部又按紧急程度组织成四种“调度类”,各种“调度类”管理自己的就绪进程。各种“调度类”内部的进程间使用优先级进一步区分各自的重要程度。进程调度解决的就是如何从这些就绪进程队列中各自选择一个进程到对应CPU上去运行的问题,而负载均衡就是在各个处理器上负载严重不均的时候将繁忙处理器rq上的进程迁移到空闲CPU的rq之上,因此rq是调度和负载均衡的基础数据结构。上述工作可以简单地用图32表示。 图32就绪进程组织与调度、均衡示意图图32展示了4个CPU系统上的例子,每个CPU都有各自的rq,每个CPU上的rq管理着四种类型的任务——分别对应STOP、RT、CFS和IDLE调度类,优先级依次递减。实时进程属于RT调度类,普通进程属于CFS完全公平调度类。调度程序从各个rq数据结构中挑选一个进程作为对应CPU运行的任务,在合适的时机将现有任务撤下CPU而换上另一个更紧迫的任务。由于图32例子中没有STOP类进程,因此CPU0和CPU1上执行的是实时RT类任务,而CPU2上执行的是普通CFS类任务,CPU3上没有有效进程因此执行IDLE类任务。随着时间的推进,各个处理器会交替地执行本地的各个就绪进程。对于CPU3处于空闲IDLE状态,负载均衡器会将其他CPU上的就绪进程迁移过来,避免处理器忙闲不一的状态。此例子中CPU0或CPU1上的高优先级实时任务可通过负载均衡迁移到CPU2和CPU3上。通常只能创建RT类和CFS类的进程,前者是实时进程,后者是普通进程。 3.1.2全系统的调度统计Linux在/proc/sched_debug中给出了整个系统范围的调度相关统计信息,如屏显34所示。其内容分成多个小节,前面是系统整体信息,后面按逻辑CPU分成多个部分。由于系统是双核系统,因此屏显34中对应有cpu#0和cpu#1两部分的统计信息,内部再分成逻辑CPU整体信息和CFS运行队列、RT运行队列和本逻辑CPU上的就绪进程列表三个小节。屏显34/proc/sched_debug信息[lqm@localhost ~]$cat /proc/sched_debug Sched Debug Version: v0.11, 3.10.0-514.el7.x86_64 #1ktime: 3914668.820838sched_clk : 3913632.013169cpu_clk : 3913632.013216jiffies : 4298581965sched_clock_stable() : 1
sysctl_sched.sysctl_sched_latency : 12.000000 .sysctl_sched_min_granularity : 10.000000 .sysctl_sched_wakeup_granularity : 15.000000 .sysctl_sched_child_runs_first : 0 .sysctl_sched_features : 77435 .sysctl_sched_tunable_scaling : 1 (logaritmic)
cpu#0, 3591.684 MHz0号处理器的统计信息 .nr_running: 3本处理器上的就绪进程个数(包括正在运行的进程) .load : 3072 本处理器上的即时负载信息 .nr_switches : 202197 本处理器上累计的进程切换次数 .nr_load_updates : 3587360 .nr_uninterruptible : 5 本处理器上进入uninterruptible的进程数 .next_balance : 4298.582045 本处理器下次执行负载均衡的时间.curr->pid : 4696 当前运行的进程号 .clock : 3913632.398703 .cpu_load[0] : 3072 本处理器上的历史负载信息
.cpu_load[1] : 2560 本处理器上的历史负载信息 .cpu_load[2] : 2302 本处理器上的历史负载信息 .cpu_load[3] : 2109 本处理器上的历史负载信息 .cpu_load[4] : 1836 本处理器上的历史负载信息 .avg_idle : 1000000 .max_idle_balance_cost : 500000
cfs_rq[0]:/0号处理器上CFS队列的统计信息.exec_clock : 0.000000 .MIN_vruntime : 3578565.340659 .min_vruntime : 3578571.340659 .max_vruntime : 3578571.341220 .spread : 6.000561 .spread0 : 0.000000 .nr_spread_over : 0 .nr_running : 3 本处理器CFS队列上的就绪进程 .load : 3072 .runnable_load_avg : 2046 .blocked_load_avg : 0 .tg_load_avg : 0 .tg_load_contrib : 0 .tg_runnable_contrib : 0 .tg->runnable_avg : 0 .tg->cfs_bandwidth.timer_active: 0 .throttled : 0 .throttle_count : 0 .avg->runnable_avg_sum : 46365 .avg->runnable_avg_period : 46365
rt_rq[0]:/.rt_nr_running: 0 .rt_throttled : 0 .rt_time : 0.000000 .rt_runtime : 950.000000
runnable tasks:
task PID tree-key switches prio wait-time sum-exec sum-sleep————————————————————- ksoftirqd/033577023.584799 1291120 0.00000018.2300170.000000 0 / kworker/0:0H5 2007.658749 6100 0.000000 0.0139990.000000 0 /migration/07 0.000000 4590 0.000000 3.8332870.000000 0 / ……bash 376634246.354016 103120 0.00000069.3612230.000000 0 / HelloWorld-loop 38663578571.34122016776120 0.0000003765740.5897190.000000 0 / kworker/0:0 40331525959.942626 1028120 0.00000085.3785010.000000 0 / kworker/0:2 41653578565.340659 4067120 0.000000 350.7518100.000000 0 /Rcat46963578571.576358 0120 0.000000 5.1254320.000000 0 /
cpu#1, 3591.684 MHz1号处理器的统计信息 .nr_running : 1 .load : 1024 .nr_switches : 785504 ……
cfs_rq[1]:/ ……rt_rq[1]:/.rt_nr_running : 0 .rt_throttled : 0 .rt_time : 0.000000 .rt_runtime : 950.000000
runnable tasks: task PID tree-key switches prio wait-time sum-exec sum-sleep————————————————————- systemd 1 282499.295959 4443 120 0.000000 2090.978241 0.000000 0 / kthreadd 2 282563.909370 144 120 0.000000 3.268203 0.000000 0 /……本例中cpu#0有CFS类型的cfs_rq[0]和RT类型的rt_rq[0]两个不同调度类型的进程队列统计信息。其中cfs_rq[0]上有就绪的普通CFS进程nr_running=3个,而rt_rq[0]上还没有就绪的实时进程rt_nr_running=0。curr→pid指出当前正在cpu#0上运行的是4696号进程。在逻辑CPU整体统计信息中还给出了即时负载信息load、历史负载信息cpu_load[0]~[4]和进行下一次负载均衡的时间next_balance,这些是负载均衡的重要依据。cpu#0上就绪进程则在“runnable tasks:”标签的后面,各列信息分别是: switches切换次数、prio优先级、waittime等待IO的时间、sumexec运行时间以及sumsleep阻塞时间。对应于cpu#1则有cfs_rq[1]和rt_rq[1]等内容。另外,在/proc/schedstat中有其他的调度统计信息,由于没有自带标注,因此难以解读。更多信息可以参考Linux源代码目录中的文档子目录“linux3.10.0/Documentation/scheduler/”下的“schedstats.txt”。在屏显35 cpu 后面的9个数字中,后三个非零的数值分别是该处理器上进程执行时间总和(以jiffies计)、进程等待运行的时间总和(以jiffies计)以及时间片总数。domain 后面的两个数字是CPU掩码,3表示二进制“11”,该调度域包含cpu0和cpu1两个处理器,如屏显35所示。屏显35/proc/schedstat [lqm@localhost ~]$cat /proc/schedstatversion 15timestamp 4317490556cpu0 0 0 0 0 0 0 17470898269689 145575976930 627107domain0 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0cpu1 0 0 0 0 0 0 1059838105220 226443483586 1973678domain0 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0[lqm@localhost ~]$
3.2进程状态及其转变在观察调度行为前,我们还需要学习一下Linux中进程的状态表示。Linux是一个多用户、多任务的系统,可以同时运行多个用户的多个程序,这就必然会产生很多进程共享使用CPU——拥有CPU的进程可以运行而未获得CPU的进程只能暂停运行。也就是说,每个进程除了正在运行(拥有CPU的进程)外还会有其他不同的状态。其中只有就绪的进程才可能获得调度运行,它们的状态在Linux中都是TASK_RUNNING(并不区分正在CPU上运行还是在就绪队列上等待调度)。3.2.1进程状态在Linux内核代码中,这些状态的具体编码如代码31所示,我们将常见状态做如下简单分析。代码31进程状态 (linux3.13/include/linux/sched.h) 135 #define TASK_RUNNING0 136 #define TASK_INTERRUPTIBLE 1 137 #define TASK_UNINTERRUPTIBLE 2 138 #define __TASK_STOPPED 4 139 #define __TASK_TRACED 8 140 / in tsk→exit_state / 以下两个状态出现在task_struct→exit_state 141 #define EXIT_ZOMBIE 16 142 #define EXIT_DEAD 32 143 / in tsk→state again / 144 #define TASK_DEAD 64 145 #define TASK_WAKEKILL这个不是基本状态,表示即使在TASK_UNINTERRUPTIBLE状态下也可以响应致命信号(终止进程)。 128 146 #define TASK_WAKING 256 147 #define TASK_PARKED 512在get_task_state()返回TASK_INTERRUPTIBLE 148 #define TASK_STATE_MAX 10241. TASK_RUNNING可执行状态,包括就绪状态和正在CPU上执行状态。只有在该状态的进程才可能在CPU上运行。同一时刻可能有多个进程处于可执行的就绪状态,多核平台上有超过一个进程正运行在CPU上。这些进程的task_struct结构(进程控制块)被挂入对应CPU的可执行队列(运行队列、就绪队列)中,一个进程最多只能出现在一个CPU的运行队列中。很多操作系统教科书将正在CPU上执行的进程定义为RUNNING状态,而将可执行但是尚未被调度执行的进程定义为READY状态,这两种状态在Linux系统中统一定义为TASK_RUNNING状态,对应的状态编码数值为0。用ps命令或/proc/PID/status查看进程时,可执行状态的进程显示为R。2. TASK_INTERRUPTIBLE可中断的睡眠状态,也是操作系统课程中所谓的阻塞状态。处于这个状态的进程因为等待某事件的发生(通常是IO操作,例如等待socket连接、等待信号量)而被阻塞。这些进程的task_struct结构从运行队列中取下并放入对应事件的等待队列中。当所等待的事件发生时(由外部中断触发或由其他进程触发),对应等待队列中的一个或多个进程将被唤醒,重新挂回到就绪队列中。通过ps命令会看到,除非计算机的负载很高,一般情况下进程列表中的绝大多数进程都处于TASK_INTERRUPTIBLE状态(用ps命令查看进程状态时显示为S状态)。3. TASK_UNINTERRUPTIBLE不可中断的睡眠状态。与TASK_INTERRUPTIBLE状态类似,进程处于阻塞状态,但是此刻进程是不会因信号的到来而被唤醒。绝大多数情况下,进程处在睡眠状态时,总是应该能够响应异步信号。由于不响应信号,所以即使用kill9命令也杀不死这样的进程。这时用ps命令或/proc/PID/status查看进程状态时显示为D状态。而TASK_UNINTERRUPTIBLE状态存在的意义就在于内核的某些处理流程是不能被打断的。如果响应异步信号,程序的执行流程中就会被插入一段用于处理异步信号的流程,于是原有的流程就被中断了。在进程对某些硬件进行操作时(例如进程调用read系统调用,对某个设备文件进行读操作,最终执行到相应设备驱动的代码,并与对应的物理设备进行交互),可能需要使用TASK_UNINTERRUPTIBLE状态对进程进行保护,以避免进程与设备交互的过程被打断,造成设备陷入不可控的状态。该状态的进程只能用wake_up()函数唤醒(例如从驱动程序的中断处理代码中发出调用)。这种情况下的TASK_UNINTERRUPTIBLE状态总是非常短暂的,通过ps命令基本上不可能查看到。Linux系统中也存在容易查看的TASK_UNINTERRUPTIBLE状态。执行vfork系统调用后,父进程将进入TASK_UNINTERRUPTIBLE状态,直到子进程调用exit()或execve()函数。4. TASK_STOPPED或TASK_TRACED暂停状态或跟踪状态。按Ctrl Z键向进程发送一个SIGSTOP信号,它就会因响应该信号而进入TASK_STOPPED状态(除非该进程本身处于TASK_UNINTERRUPTIBLE状态而不响应信号)。SIGSTOP与SIGKILL信号一样是强制的,不允许用户进程通过signal系列的系统调用重新设置相应的信号处理函数。向进程发送一个SIGCONT信号,可以让其从TASK_STOPPED状态恢复到TASK_RUNNING状态。用ps命令或/proc/PID/status查看这类进程时,显示的是T状态。后台进程执行getchar()等阻塞操作时会进入T状态。当进程正在被跟踪时,它处于TASK_TRACED这个特殊的状态。“正在被跟踪”指的是进程暂停下来,等待跟踪它的进程对它进行操作。例如在gdb中对被跟踪的进程设置一个断点,进程在断点处停下来的时候就处于TASK_TRACED状态。而在其他时候,被跟踪的进程还是处于前面提到的那些状态。对于进程本身来说,TASK_STOPPED和TASK_TRACED状态很类似,都表示进程暂停下来。而TASK_TRACED状态相当于在TASK_STOPPED之上多了一层保护,处于TASK_TRACED状态的进程不能响应SIGCONT信号而被唤醒。只能等到调试进程通过ptrace系统调用执行PTRACE_CONT、PTRACE_DETACH等操作(通过ptrace系统调用的参数指定操作)或调试进程退出,被调试的进程才能恢复TASK_RUNNING状态。5. TASK_DEAD(EXIT_ZOMBIE)退出状态,且成为僵尸进程。进程在退出的过程中,处于TASK_DEAD状态。在这个退出过程中,进程占有的所有资源将被回收——除了task_struct结构以及少数资源以外。于是进程就只剩下task_struct这个空壳,故称为僵尸进程。之所以保留task_struct,是因为task_struct里面保存了进程的退出码以及一些统计信息,而其父进程很可能会关心这些信息。例如在shell中,$?变量就保存了最后一个退出前台进程的退出码,而这个退出码往往被作为if语句的判断条件。保留完整的task_struct结构而不仅仅是退出状态,因为在内核中已经建立了从pid到task_struct查找关系,还有进程间的父子关系,便于父进程查找。父进程通过wait系列的系统调用(如wait4、waitid)来等待某个或某些子进程的退出,并获取它们的退出信息。然后父进程的wait系列系统调用会将子进程的“尸体”(task_struct)也释放掉。子进程在退出的过程中,内核会给其父进程发送一个信号,通知父进程来收拾残留资源。这个信号默认是SIGCHLD,但是在通过clone系统调用创建子进程时,可以设置这个信号。只要父进程不退出且没有对已结束的子进程执行wait系统调用,这个子进程就处于僵尸状态(参见2.2.2)并一直持有task_struct。但是如果父进程结束运行,会将它的所有子进程都托管给别的进程,使之成为别的进程的子进程,可以是退出进程所在进程组的下一个进程(如果存在的话)或者是1号init进程,由init进程消灭僵尸进程。用ps命令或/proc/PID/status查看这些进程的时候显示的是Z状态。6. TASK_DEAD(EXIT_DEAD)退出状态,且进程即将被销毁。进程在退出过程中也可能不会保留它的task_struct,比如这个进程是多线程程序中被分离过的线程。或者父进程通过设置SIGCHLD信号的handler为SIG_IGN显式地忽略了SIGCHLD信号,子进程结束后将被置于EXIT_DEAD退出状态,这意味着接下来的内核代码立即就会将该进程彻底释放。所以EXIT_DEAD状态是非常短暂的,几乎不可能通过ps命令查看到(显示为X状态)。7. ps命令查看进程状态 我们用ps aux查看系统中全部进程的状态获得如屏显36的输出,其中STAT列是BSD的格式进程状态。这些状态符号的具体含义可以用man ps命令查看,具体如下: D表示不可中断睡眠状态uninterruptible sleep(通常由于IO),R表示正在运行或在就绪队列中,S表示可中断睡眠状态,T表示由作业控制信号停止,t表示被跟踪,Z表示僵尸状态,W表示换出(从内核2.6开始无效),X表示死掉的进程(几乎不可能观察到)。另外有一些BSD风格的修饰符号:
评论
还没有评论。