Linux/UNIX系统编程手册-进程

Linux/UNIX系统编程手册

[德] Michael Kerrisk

第6章 进程
第24章 进程的创建
第25章 进程的终止
第26章 监控子进程

进程

进程和程序(Processes and Programs)

进程是一个可执行程序的实例(A process is an instance of an executing program).

程序(program)是包含了一系列信息的文件,这些信息描述了如何在运行时创建一个进程,所包含的信息有:

  • 二进制格式标识(Binary format identification): 每个程序文件都包含用于描述可执行文件格式的元信息(metainformation)。内核利用此信息来解释文件中的其他信息。大多数UNIX(包括Linux)采用Executable
    and Linking Format (ELF).
  • 机器语言指令(Machine-language instructions): 对程序算法进行编码
  • 程序入口地址(Program entry-point address):标识程序开始执行时起始指令位置
  • 数据(Data): 变量初始值和程序使用的字面常量(literal constant)
  • 符号表及重定位表(Symbol and relocation tables): 描述程序中函数和变量的位置及名称。
  • 共享库和动态链接信息(Shared-library and dynamic-linking information): 程序运行需要的共享库,以及加载共享库的动态链接器的路径名
  • 其他信息: 描述如何创建进程

可以用一个程序创建多个进程。

进程是由内核定义的抽象的实体,并为该实体分配用以执行程序的各项系统资源。

进程号和父进程号

进程号(PID),是用来唯一标识系统中某个进程的一个整数。对系统调用来说,进程号可以作为参数传入,kill()系统调用;也可以作为返回值,比如getpid()系统调用。

Linux内核限制进程号需要<=32767, 可以调整。

$ cat /proc/sys/kernel/pid_max
32768

每个进程都有一个创建自己的父进程,使用系统调用getppid()获取父进程的进程号。

使用pstree命令可以查看进程树。

进程内存布局(Memory Layout of a Process)

每个进程所分配的内存由很多部分组成,通常称为“段(segments)”,或者“区(section)”:

  • 文本段(text segment): 包含进程运行的机器语言指令。文本段具有只读属性,同时可共享,使多个进程使用同一份程序代码拷贝。
  • 初始化的数据段(initialized data segment): 包含显示初始化的全局变量和静态变量。
  • 未初始化数据段(uninitialized data segment):也被称为BSS(block started by symbol)。 包含未进行显式初始化的全局变量和静态变量。程序启动之前系统将本段内所有内存初始化为0。
  • 栈(stack): 动态变化的segment,由栈帧(stack frames)组成。系统会为每个当前调用的函数分配一个栈帧,其中存储函数的局部变量,实参和返回值。
  • 堆(heap): 在运行时动态分配内存的区域。堆顶端被称为program break。

size命令可显示文本段,初始化和未初始化数据段(bss)的大小

> $ man size
NAME
       size - list section sizes and total size.
...
> $ size hello
   text    data     bss     dec     hex filename
   1230     548       4    1782     6f6 hello

虚拟内存管理(Virtual Memory Management)

进程的内存布局存在与虚拟内存(Virtual Memory)中。虚拟内存的规划之一就是将每个程序使用的内存切割成小型的、固定大小的“页”(page)单元。相应地,将RAM换成一系列与“页”大小相同的页帧。

为支持这一组织形式,内核为每个进程维护一张页表(page table),用于记录每页在进程虚拟地址空间的位置。

虚拟内存管理使进程的虚拟地址空间与RAM物理地址空间隔离开来。

栈和栈帧(the stack and stack frame)

函数的调用和返回使栈的增长和收缩呈线性。栈驻留在内存的高端比你高向下增长(朝堆的方向)。专用寄存器--栈指针(stack pointer),用于跟踪当前栈顶。每次调用函数和返回函数时,都是在栈上新增和移去栈帧。

栈帧(user stack), 一般指用户栈,区分与内核栈,包含一下信息:

  • 函数实参和局部变量
  • (函数)调用的链接信息:每个函数都会用到一些CPU寄存器,比如程序计数器。比如调用另一个函数时,保存当前寄存器状态,以便返回时恢复。

命令行参数(command-line argument),argc, argv

  • int argc:命令行参数的个数
  • char *argv[]: 指向命令行参数的指针数组,每一参数都是以空字符('\0')结尾的字符串.

程序可以通过/proc/PID/cmdline文件访问任一进程的命令行参数,每个参数都以空(NULL)字节终止。

argv和environ(环境变量)数组,以及这些参数最初只想的字符串,都主流在进程栈上的一个单一、连续的内存区域。

进程的创建

创建新进程: fork()系统调用

fork()创建一个新进程(child),几近于对调用进程(parent)的翻版

#include <unistd.h>
pid_t fork(void);
In parent: returns process ID of child on success, or –1 on error;
in successfully created child: always returns 0

完成对其调用后将存在两个进程,每个进程都会从fork()的返回处继续执行,程序代码可通过fork()的返回值来区分父子进程。在父进程中,fork()将返回新创建子进程的进程ID,在子进程中则返回0。
子进程也可调用getpid(), getppid()分别获得自身进程以及父进程的ID。

执行fork()时候,子进程会获得父进程所有文件描述符的副本,也即父子进程共享打开的文件及其属性。

从概念上讲,fork()认作对父进程程序代码段,数据段,堆栈的拷贝,实际上,子进程一般会替换代码段,并重新初始化数据,堆栈,全拷贝就造成了浪费。因此UNIX采用两种技术来避免这种浪费:

  • 内核将每一进程的代码段标记为只读,父子进程共享该代码段。
  • 对于数据段,堆栈中各页,内核采用写时复制技术(copy-on-write)。即内核会捕捉进程中针对页的修改企图,并为将要修改的页创建拷贝。

fork()之后的竞争条件(race condition)

竞争表现在调用fork()后,无法确定父、子进程谁将率先访问CPU。Linux在版本升级中,多次调整默认优先的进程。
由于会产生所谓“竞争条件”的错误,不应对fork()之后执行父、子进程的特定顺序做任何假设。如若需要保证执行顺序,需要采用同步技术,包括信号量(semaphore)、文件锁(file lock)以及进程间经由管道(pipe)的消息发送。

进程的终止

_exit()和exit()

进程可能通过两种方式终止:

  • 异常(abnormal)终止:接受到终止信号(signal),可能产生核心转储(core dump)
  • 进程使用系统调用_exit()自主终止
#include <unistd.h>
void _exit(int status);

_exit()的status参数定义了进程的终止状态,父进程可以调用wait()获取该状态。虽然定义为int类型,但仅有低8位可以被父进程使用。调用_exit()的程序总会成功终止,即使从不返回。

一般使用库函数exit()来终止进程,它会在调用_exit()前执行各种动作:

  • 调用退出处理程序(通过atexit()和on_exit()注册的函数)
  • 刷新stdio流缓冲区
  • 使用由status提供的值执行_exit()系统调用

监控子进程

父进程需要了解其某个子进程何时改变了状态,用于监控子进程有两种方式:

  • 系统调用wait()
  • 信号SIGCHLD

等待子进程

系统调用wait()

wait()等待进程的任一子进程终止,同时在参数status所指向的缓冲区中返回该子进程的终止状态

#include <sys/wait.h>
pid_t wait(int *status);
    Returns process ID of terminated child, or –1 on error

wait()执行一下动作:

  • 如果调用之前还没有子进程终止,则一直阻塞,如果有,则立即返回
  • 如果status非空,那么关于子进程如何终止的信息则会通过status指向的整型变量返回
  • 内核将会为父进程下所有子进程的运行总量追加进程CPU时间以及资源使用数据
  • 将终止子进程的ID作为wait()的结果返回

系统调用waitpid()

wait()存在诸多限制,而waitpid()则意在突破这些限制

  • 无法指定某个特定的子进程,只能按循序等待下一个子进程终止
  • 没有进程退出,则wait()总是阻塞
  • wait()只能发现终止的子进程,而对终止原因及恢复执行情况无能为力
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
    Returns process ID of child, 0 (see text), or –1 on error

等待状态值

wait()和waitpid()返回的status值,可用来区分一下子进程事件:

  • 子进程调用_exit()或exit()而终止,并指定一个整型值作为退出状态
  • 子进程收到未处理信号终止
  • 子进程因为信号而暂停,并以WUNTRACED标志调用waitpid()
  • 子进程因收到信号SIGCONT而恢复,并以WCONTINUED标志调用waitpid()

同时还有waitid(), wait3()和wait4()

孤儿进程和僵尸进程(Orphan and ZOmbie)

  • 孤儿进程:父进程先于子进程终止,此时init会接管该进程,对getppid()的调用将返回1
  • 僵尸进程,父进程在wait()之前,子进程就已终止,内核会将子进程转为僵尸进程,即释放资源,但是保留该进程在进程表里的一条记录,包含进程id,终止状态,资源使用数据等信息。

父进程应执行wait()方法,以确保系统中总是能够清理那些死去的子进程。

SIGCHLD信号

无论一个子进程何时终止,系统都会向其父进程发送SIGCHLD信号。
对该信号的默认处理时将其忽略,可以通过设置信号处理程序signal()或sigaction()来捕获,同时编写信号处理函数使用wait()来处理僵尸进程。

原文链接
https://sun2y.me

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容