目录

1、什么是进程

2、进程号

2.1、getpid()函数

2.2、getppid()函数

3、进程的环境变量

4、进程的内存布局

5、进程的虚拟地址空间

5.1、为什么需要引入虚拟地址

6、fork()函数

6.1、fork()函数使用场景

7、vfork函数

8、fork()和vfork()的区别

9、父、 子进程间的文件共享

10、监视子进程

10.1、wait()函数

10.2、wait()函数执行流程

10.3、wait()函数的限制

10.4 waitpid()函数

10.5、waitid()函数...todo

11、孤儿进程与僵尸进程

11.1、孤儿进程

11.2、僵尸进程

11.3、父进程接收SIGCHLD 信号

12、执行新程序

12.1、execve()函数

12.2、exec 库函数

13、进程状态

14、进程关系

15、进程组

15.1、getpgrp()函数

15.2、getpgid()函数

15.3、setpgid()函数

15.4、setpgrp()函数

16、会话

16.1、getsid()函数

16.2、setsid()函数

17、守护进程

18、SIGHUP 信号


1、什么是进程

进程是一个动态过程,而非静态文件,它是程序的一次运行过程,当应用程序被加载到内存中运行之后它就称为了一个进程,当程序运行结束后也就意味着进程终止,这就是进程的一个生命周期。

2、进程号

每一个进程都有一个进程号(process ID,简称 PID),进程号是一个正数,用于唯一标识系统中的某一个进程。

2.1、getpid()函数

获取当前进程号。

#include <sys/types.h>
#include <unistd.h>pid_t getpid(void);

返回值:返回当前进程PID

2.2、getppid()函数

获取当前进程的父进程进程号。

#include <sys/types.h>
#include <unistd.h>pid_t getppid(void);

返回值:返回当前进程的父进程进程号

3、进程的环境变量

https://mp.csdn.net/mp_blog/creation/editor/118220737

4、进程的内存布局

代码段 CPU 执行的机器语言指令部分,文本段具有只读属性,以防止程序由于意外而修改其指令;正文段是可以共享的,即使在多个进程间也可同时运行同一段程序。
数据段 包含了显式初始化的全局变量和静态变量,当程序加载到内存中时,从可执行文件中读取这些变量的值。
未初始化数据段(BSS) 包含了未进行显式初始化的全局变量和静态变量.在程序开始执行之前,系统会将本段内所有内存初始化为 0, 可执行文件并没有为 bss 段变量分配存储空间,在可执行文件中只需记录 bss 段的位置及其所需大小,直到程序运行时,由加载器来分配这一段内存空间。
函数内的局部变量以及每次函数调用时所需保存的信息都放在此段中,每次调用函数时,函数
传递的实参以及函数返回值等也都存放在栈中。栈是一个动态增长和收缩的段,由栈帧组成,系统
会为每个当前调用的函数分配一个栈帧,栈帧中存储了函数的局部变量(所谓自动变量)、实参和
返回值。
可在运行时动态进行内存分配的一块区域,譬如使用 malloc()分配的内存空间,就是从系统堆
内存中申请分配的。

注:size 命令可以查看二进制可执行文件的文本段、数据段、 bss 段的段大小

5、进程的虚拟地址空间

在 Linux 系统中,采用了虚拟内存管理技术。每一个进程都在自己独立的地址空间中运行,在 32 位系统中,每个进程的逻辑地址空间均为 4GB, 其中用户进程享有 3G 的空间,而内核独自享有剩下的 1G 空间。

5.1、为什么需要引入虚拟地址

计算机物理内存的大小是固定的(计算机的实际物理内存),如果操作系统没有虚拟地址机制,所有的应用程序访问的内存地址就是实际的物理地址, 所以要将所有应用程序加载到内存中。

进程与进程、进程与内核相互隔离 一个进程不能读取或修改另一个进程或内核的内存数据,这是因为每一个进程的虚拟地址空间映射到了不同的物理地址空间。 提高了系统的安全性与稳定性。
共享内存 在某些应用场合下,两个或者更多进程能够共享内存。 因为每个进程都有自己的映射表,可以让不同进程的虚拟地址空间映射到相同的物理地址空间中。通常,共享内存可用于实现进程间通信
内存保护机制  譬如在多个进程共享内存时, 允许每个进程对内存采取不同的保护措施.例如,一个进程可能以只读方式访问内存,而另一进程则能够以可读可写的方式访问。
无需关心链接地址 编译应用程序时,无需关心链接地址。当程序运行时,要求链接地址与运行地址一致,在引入了虚拟地址机制后,便无需关心这个问题。

6、fork()函数

调用 fork()函数的进程称为父进程,由 fork()函数创建出来的进程被称为子进程。

#include <unistd.h>pid_t fork(void);

返回值:fork()调用成功后,将会在父进程中返回子进程的 PID,而在子进程中返回值是 0;如果调用失败,父进程返回值-1,不创建子进程,并设置 errno

注:fork()调用成功后,子进程和父进程会继续执行 fork()调用之后的指令,子进程、父进程各自在自己的进程空间中运行。事实上,子进程是父进程的一个副本, 譬如子进程拷贝了父进程的数据段、堆、栈以及继承了父进程打开的文件描述符,父进程与子进程并不共享这些存储空间,这是子进程对父进程相应部分存储空间的完全复制虽然子进程是父进程的一个副本,但是对于程序代码段(文本段)来说, 两个进程执行相同的代码段,因为代码段是只读的, 也就是说父子进程共享代码段,在内存中只存在一份代码段数据。

6.1、fork()函数使用场景

1)父进程希望子进程复制自己,使父进程和子进程同时执行不同的代码段。这在网络服务进程中是常见的,父进程等待客户端的服务请求,当接收到客户端发送的请求事件后,调用 fork()创建一个子进程,使子进程去处理此请求、而父进程可以继续等待下一个服务请求。

2)一个进程要执行不同的程序。这种情况,通常在子进程从 fork()函数返回之后立即调用 exec 族函数来实现。

7、vfork函数

除了 fork()系统调用之外, Linux 系统还提供了 vfork()系统调用用于创建子进程。使用 fork()系统调用的代价是很大的,它复制了父进程中的数据段和堆栈段中的绝大部分内容,这将会消耗比较多的时间, 效率会有所降低,而且太浪费。原因有很多,其中之一在于, fork()函数之后子进程通常会调用 exec 函数, 也就是 fork()第二种使用场景下, 这使得子进程不再执行父程序中的代码段,而是执行新程序的代码段, 从新程序的 main 函数开始执行、 并为新程序重新初始化其数据段、堆段、栈段等; 在这种情况下,子进程并不需要用到父进程的数据段、堆段、栈段(譬如父程序中定义的局部变量、全局变量等)中的数据, 此时就会导致浪费时间、 效率降低。

#include <sys/types.h>
#include <unistd.h>pid_t vfork(void);

返回值:fork()调用成功后,将会在父进程中返回子进程的 PID,而在子进程中返回值是 0;如果调用失败,父进程返回值-1,不创建子进程,并设置 errno。

注:在正式的使用场合下,一般应在子进程中立即调用 exec,如果 exec 调用失败,子进程则应调用_exit()退出(vfork 产生的子进程不应调用 exit 退出,因为这会导致对父进程 stdio 缓冲区的刷新和关闭)。

8、fork()和vfork()的区别

1) vfork()与 fork()一样都创建了子进程,但 vfork()函数并不会将父进程的地址空间完全复制到子进程中,因为子进程会立即调用 exec(或_exit) ,于是也就不会引用该地址空间的数据。不过在子进程调用 exec 或_exit 之前,它在父进程的空间中运行、 子进程共享父进程的内存。这种优化工作方式的实现提高的效率; 但如果子进程修改了父进程的数据(除了 vfork 返回值的变量)、进行了函数调用、或者没有调用 exec 或_exit 就返回将可能带来未知的结果。
2) vfork()保证子进程先运行, 子进程调用 exec 之后父进程才可能被调度运行。fork()不能保证是子进程先运行还是父进程先运行。

注:虽然 vfork()系统调用在效率上要优于 fork(),但是 vfork()可能会导致一些难以察觉的程序 bug,所以尽量避免使用 vfork()来创建子进程。虽然 fork()在效率上并没有 vfork()高,但是现代的 Linux 系统内核已经采用了写时复制技术来实现 fork(),其效率较之于早期的 fork()实现要高出许多,除非速度绝对重要的场合,程序当中应舍弃 vfork()而使用 fork()。

9、父、 子进程间的文件共享

子进程拷贝了父进程的文件描述符表,使得父、子进程中对应的文件描述符指向了相同的文件表, 也意味着父、子进程中对应的文件描述符指向了磁盘中相同的文件,因而这些文件在父、子进程间实现了共享。譬如如果子进程更新了文件偏移量,那么这个改变也会影响到父进程中相应文件描述符的位置偏移量。

10、监视子进程

10.1、wait()函数

系统调用 wait()可以等待进程的任一子进程终止,同时获取子进程的终止状态信息(一次 wait函数调用只能处理一次)

#include <sys/types.h>
#include <sys/wait.h>pid_t wait(int *status);

参数 status: 参数 status 用于存放子进程终止时的状态信息,参数 status 可以为 NULL,表示不接收子进程终止时的状态信息。参数 status 不为 NULL 的情况下,则 wait函数会将子进程的终止时的状态信息存储在它指向的 int 变量中,可以通过以下宏来检查 status 参数:

WIFEXITED(status) 如果子进程正常终止,则返回 true
WEXITSTATUS(status)

返回子进程退出状态,是一个数值,其实就是子进程调用_exit()或 exit()时指定的退出状态

WIFSIGNALED(status) 如果子进程被信号终止,则返回 true。
WTERMSIG(status) 返回导致子进程终止的信号编号。如果子进程是被信号所终止,则可以通过此宏获取终止子进程的信号
WCOREDUMP(status) 如果子进程终止时产生了核心转储文件,则返回 true

返回值: 若成功则返回终止的子进程对应的进程号;失败则返回-1。

10.2、wait()函数执行流程

如果其所有子进程都还在运行 则 wait()会一直阻塞等待,直到某一个子进程终止
该进程并没有子进程(该进程并没有需要等待的子进程) 将返回错误,也就是返回-1、并且会将 errno 设置为ECHILD
子进程当中已经有一个或多个子进程已经终止了

调用 wait函数不会阻塞。

wait函数的作用除了获取子进程的终止状态信息之外,更重要的一点,就是回收子进程的一些资源。

10.3、wait()函数的限制

1 如果父进程创建了多个子进程,使用 wait()将无法等待某个特定的子进程的完成,只能按照顺序等待下一个子进程的终止,一个一个来、谁先终止就先处理谁.
2 如果子进程没有终止,正在运行,那么 wait()总是保持阻塞,有时我们希望执行非阻塞等待
3 使用 wait()只能发现那些被终止的子进程,对于子进程因某个信号(譬如 SIGSTOP 信号)而停止(注意,这里停止指的暂停运行),或是已停止的子进程收到 SIGCONT 信号后恢复执行的情况就无能为力了。

10.4 waitpid()函数

waitpid()可以解决wait()的限制。

#include <sys/types.h>
#include <sys/wait.h>pid_t waitpid(pid_t pid, int *status, int options);

参数 pid:用于表示需要等待的某个具体子进程,关于参数 pid 的取值范围如下:

pid 大于 0 表示等待进程号为 pid 的子进程
pid 等于 0 等待与调用进程(父进程)同一个进程组的所有子进程
pid 小于-1 会等待进程组标识符与 pid 绝对值相等的所有子进程
pid 等于-1 则等待任意子进程。

参数 status: 与 wait()函数的 status 参数意义相同。

参数 options 是一个位掩码,可以包括 0 个或多个如下标志:

WNOHANG 如果子进程没有发生状态改变(终止、暂停),则立即返回,也就是执行非阻塞等待,可以实现轮训 poll,通过返回值可以判断是否有子进程发生状态改变若返回值等于 0 表示没有发生改变
WUNTRACED 除了返回终止的子进程的状态信息外,还返回因信号而停止(暂停运行)的子进程状态信息
WCONTINUED 返回那些因收到 SIGCONT 信号而恢复运行的子进程的状态信息

返回值: 返回值与 wait()函数的返回值意义基本相同,在参数 options 包含了 WNOHANG 标志的情况下,返回值会出现 0

10.5、waitid()函数

此函数类似于waitpid,但提供了更多的灵活性。waitid允许一个进程指定要等待的子进程。但它使用单独的参数表示要等待的子进程的类型,而不是将此与进程ID或进程组ID组合成一个参数。

#include <sys/wait.h>int waitid( idtype_t idtype, id_t id, siginfo_t *infop, int options );

参数 idtype:

 常量 说明
P_PID 等待一个特定的进程:id包含要等待子进程的进程ID
P_PGID 等待一个特定进程组中的任一子进程:id包含要等待子进程的进程组ID
P_ALL 等待任一子进程:忽略id

参数 id:与idtype的值相关。

参数 infop:指向siginfo结构的指针。该结构包含了有关引起子进程状态改变的生成信号的详细信息

参数 options :

WCONTINUED 等待一个进程,它以前曾被暂停,此后又已继续,但其状态尚未报告
WEXITED 等待已退出的进程
WNOHANG 如无可用的子进程退出状态,立即返回而非阻塞
WNOWAIT 不破坏子进程退出状态。该子进程退出状态可由后续的wait、waitid或waitpid调用取得
WSTOPPED 等待一个进程,它已经暂停,但其状态尚未报告

返回值:若成功则返回0,若出错则返回-1

11、孤儿进程与僵尸进程

11.1、孤儿进程

父进程先于子进程结束,也就是意味着,此时子进程变成了一个“孤儿”,我们把这种进程就称为孤儿进程。

注:在 Linux 系统当中,所有的孤儿进程都自动成为 init 进程(进程号为 1)的子进程, 换言之, 某一子进程的父进程结束后,该子进程调用 getppid()将返回 1, init 进程变成了孤儿进程的“养父”; 这是判定某一子进程的“生父”是否还“在世”的方法之一。

11.2、僵尸进程

自身已终止、但其父进程仍在运行且尚未等待该子进程的进程。当子进程终止时,内核会给其父进程产生一个SIGCHLD信号,不过父进程没有捕获这个信号(该信号的默认行为就是忽略)。

注:要是父进程没有调用waitpid()/wait()等,而是直接终止,那么子进程将成为托孤给init进程的孤儿进程,内核将为此向init进程发送一个另外的SIGCHLD信号,init进程随后将取得该僵尸进程的终止状态。

11.3、父进程接收SIGCHLD 信号

当发生以下两种情况时,父进程会收到该信号:
1) 当父进程的某个子进程终止时,父进程会收到 SIGCHLD 信号;
2) 当父进程的某个子进程因收到信号而停止(暂停运行)或恢复时,内核也可能向父进程发送该信号。

当调用信号处理函数时,会暂时将引发调用的信号添加到进程的信号掩码中(除非 sigaction()指定了 SA_NODEFER 标志), 这样一来,当 SIGCHLD 信号处理函数正在为一个终止的子进程“收尸”时,如果相继有两个子进程终止,即使产生了两次 SIGCHLD 信号,父进程也只能捕获到一次 SIGCHLD 信号,结果是,父进程的 SIGCHLD 信号处理函数每次只调用一次 wait(),那么就会导致有些僵尸进程成为“漏网之鱼”。

解决方案就是:在 SIGCHLD 信号处理函数中循环以非阻塞方式来调用 waitpid(),直至再无其它终止的子进程需要处理为止,所以,通常 SIGCHLD 信号处理函数内部代码如下所示

while (waitpid(-1, NULL, WNOHANG) > 0)
continue;

上述代码一直循环下去,直至 waitpid()返回 0,表明再无僵尸进程存在;或者返回-1,表明有错误发生。应在创建任何子进程之前,为 SIGCHLD 信号绑定处理函数。

12、执行新程序

虽然可以直接在子进程分支编写子进程需要运行的代码,但是不够灵活,扩展性不够好, 所以就出现了 exec 操作。

12.1、execve()函数

系统调用 execve()可以将新程序加载到某一进程的内存空间,通过调用 execve()函数将一个外部的可执行文件加载到进程的内存空间运行,使用新的程序替换旧的程序,而进程的栈、数据、以及堆数据会被新程序的相应部件所替换,然后从新程序的 main()函数开始执行。

#include <unistd.h>int execve(const char *filename, char *const argv[], char *const envp[]);

参数 filename:指向需要载入当前进程空间的新程序的路径名,既可以是绝对路径、也可以是
相对路径。
参数 argv:指定了传递给新程序的命令行参数。是一个字符串数组, 该数组对应于 main(int argc,char *argv[])函数的第二个参数 argv,且格式也与之相同,是由字符串指针所组成的数组,以 NULL 结束。argv[0]对应的便是新程序自身路径名。
参数 envp: 字符串指针数组, 指定了新程序的环境变量列表, 参数 envp 其实对应于新程序的 environ 数组,同样也是以 NULL 结束,所指向的字符串格式为 name=value。
返回值: execve 调用成功将不会返回;失败将返回-1,并设置 errno。

12.2、exec 库函数

#include <unistd.h>int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);

l(list):参数地址列表,以空指针结尾。

v(vector):存有各参数地址的指针数组的地址。

p(path):按 PATH 环境变量指定的目录搜索可执行文件。

e(environment):存有环境变量字符串地址的指针数组的地址。

13、进程状态

Linux 系统下进程通常存在 6 种不同的状态,分为:就绪态、运行态、僵尸态、 可中断睡眠状态(浅度睡眠)、不可中断睡眠状态(深度睡眠)以及暂停态。

就绪态 指该进程满足被 CPU 调度的所有条件,但此时并没有被调度执行,只要得到 CPU就能够直接运行;意味着该进程已经准备好被 CPU 执行,当一个进程的时间片到达,操作系统调度程序会从就绪态链表中调度一个进程
运行态 指该进程当前正在被 CPU 调度运行,处于就绪态的进程得到 CPU 调度就会进入运行态
僵尸态 僵尸态进程其实指的就是僵尸进程,指该进程已经结束、但其父进程还未给它“收尸”
可中断睡眠状态 可中断睡眠也称为浅度睡眠,表示睡的不够“死”,还可以被唤醒,一般来说可以通过信号来唤醒
不可中断睡眠状态 不可中断睡眠称为深度睡眠,深度睡眠无法被信号唤醒,只能等待相应的条件成立才能结束睡眠状态。把浅度睡眠和深度睡眠统称为等待态(或者叫阻塞态) ,表示进程处于一种等待状态,等待某种条件成立之后便会进入到就绪态;所以,处于等待态的进程是无法参与进程系统调度的
暂停态 暂停并不是进程的终止,表示进程暂停运行,一般可通过信号将进程暂停,譬如 SIGSTOP
信号;处于暂停态的进程是可以恢复进入到就绪态的,譬如收到 SIGCONT 信号。

14、进程关系

无关系 两个进程间没有任何关系,相互独立
父子进程关系

两个进程间构成父子进程关系,譬如一个进程 fork()创建出了另一个进程,那么这两个进程间就构成了父子进程关系,调用 fork()的进程称为父进程、而被 fork()创建出来的进程称为子进程;

注:如果父进程先于子进程结束,那么 init 进程就会成为子进程的父进程,它们之间同样也是父子进程关系。

进程组 进程组是一个或多个进程的集合,这些进程并不是孤立的,它们彼此之间或者存在父子、兄弟关系,或者在功能上有联系。

15、进程组

1) 每个进程必定属于某一个进程组、且只能属于一个进程组;
2) 每一个进程组有一个组长进程,组长进程的 ID 就等于进程组 ID;
3) 在组长进程的 ID 前面加上一个负号即是操作进程组;
4) 组长进程不能再创建新的进程组;
5) 一个进程组可以包含一个或多个进程,进程组的生命周期从被创建开始,到其内所有进程终止或离开该进程组;
6) 默认情况下,新创建的进程会继承父进程的进程组 ID。

15.1、getpgrp()函数

获取进程对应的进程组 ID。

#include <unistd.h>pid_t getpgrp(void);

返回值:成功将返回进程组 ID;失败将返回-1、并设置 errno。

15.2、getpgid()函数

获取进程对应的进程组 ID。

#include <unistd.h>pid_t getpgid(pid_t pid);

参数 pid:指定获取对应进程的进程组 ID。为 0 表示获取调用者进程的进程组 ID。

返回值:成功将返回进程组 ID;失败将返回-1、并设置 errno。

15.3、setpgid()函数

加入一个现有的进程组或创建一个进程组。将参数 pid 指定的进程的进程组 ID 设置为参数 gpid。如果这两个参数相等(pid==gpid),则由 pid 指定的进程变成为进程组的组长进程,创建了一个新的进程。

#include <unistd.h>int setpgid(pid_t pid, pid_t pgid);

参数 pid :如果等于 0,则使用调用者的进程 ID。

参数 pgid:如果等于 0,则创建一个新的进程组,由参数 pid 指定的进程作为进程组组长进程。

返回值:成功返回0,出错返回-1,并设置错误码。

注:一个进程只能为它自己或它的子进程设置进程组 ID,在它的子进程调用 exec 函数后,它就不能更改该子进程的进程组 ID 了。

15.4、setpgrp()函数

setpgrp()函数等价于 setpgid(0, 0)。

#include <unistd.h>int setpgrp(void);

返回值:成功返回0,出错返回-1,并设置错误码。

16、会话

会话是一个或多个进程组的集合。一个会话可包含一个或多个进程组但只能有一个前台进程组,其它的是后台进程组;每个会话都有一个会话首领(leader),即创建会话的进程。一个会话可以有控制终端、也可没有控制终端,在有控制终端的情况下也只能连接一个控制终端这通常是登录到其上的终端设备(在终端登录情况下)或伪终端设备(譬如通过 SSH 协议网络登录)

会话的首领进程连接一个终端之后,该终端就成为会话的控制终端,与控制终端建立连接的会话首领进程被称为控制进程;产生在终端上的输入和信号将发送给会话的前台进程组中的所有进程,譬如 Ctrl + C(产生 SIGINT 信号)、 Ctrl + Z(产生 SIGTSTP 信号)、 Ctrl + \(产生 SIGQUIT 信号) 等等这些由控制终端产生的信号。

当用户在某个终端登录时,一个新的会话就开始了; 当我们在 Linux 系统下打开了多个终端窗口时,实际上就是创建了多个终端会话。

注:控制终端只是会话中的一个进程,只有会话中的所有进程退出后,会话才会结束;很显然当程序中忽略了 SIGHUP 信号,导致该进程不会终止,所以会话也依然会存在。

16.1、getsid()函数

获取进程的会话 ID

#include <unistd.h>pid_t getsid(pid_t pid);

参数 pid:为0,则返回调用者进程的会话 ID;如果不为 0,则返回 pid 指定的进程对应的会话 ID。

返回值:成功返回会话 ID,失败则返回-1、并设置 errno。

16.2、setsid()函数

创建一个会话。如果调用者进程不是进程组的组长进程,调用 setsid()将创建一个新的会话,调用者进程是新会话的首领进程,同样也是一个新的进程组的组长进程,调用 setsid()创建的会话将没有控制终端。

#include <unistd.h>pid_t setsid(void);

返回值:成功将返回新会话的会话 ID;失败将返回-1,并设置 errno。

17、守护进程

守护进程(Daemon)是运行在后台的一种特殊进程(一般进程名后面带有 d 就表示它是一个守护进程),它独立于控制终端并且周期性地执行某种任务或等待处理某些事情的发生。

注:守护进程与终端无任何关联,用户的登录与注销与守护进程无关、不受其影响,守护进程自成进程组、自成会话,即pid=gid=sid。

长期运行

守护进程是一种生存期很长的一种进程,它们一般在系统启动时开始运行,除非强行终止,否则直到系统关机都会保持运行。

注:与守护进程相比,普通进程都是在用户登录或运行程序时创建,在运行结束或用户注销时终止,但守护进程不受用户登录注销的影响,它们将会一直运行着、直到系统关机

与控制终端脱离 在 Linux 中,系统与用户交互的界面称为终端,每一个从终端开始运行的进程都会依附于这个终端。当控制终端被关闭的时候, 该会话就会退出, 由控制终端运行的所有进程都会被终止, 这使得普通进程都是和运行该进程的终端相绑定的; 但守护进程能突破这种限制,它脱离终端并且在后台运行, 脱离终端的目的是为了避免进程在运行的过程中的信息在终端显示并且进程也不会被任何终端所产生的信息所打断。

注:TTY 一栏是问号?表示该进程没有控制终端,也就是守护进程,其中 COMMAND 一栏使用中括号[]括起来的表示内核线程,这些线程是在内核里创建,没有用户空间代码,因此没有程序文件名和命令行,通常采用 k 开头的名字,表示 Kernel。

17.1、编写守护进程程序

1) 创建子进程、终止父进程
父进程调用 fork()创建子进程,然后父进程使用 exit()退出,这样做实现了下面几点。第一,如果该守护进程是作为一条简单地 shell 命令启动,那么父进程终止会让 shell 认为这条命令已经执行完毕。第二,虽然子进程继承了父进程的进程组ID,但它有自己独立的进程ID,这保证了子进程不是一个进程组的组长进程,这是下面将要调用 setsid 函数的先决条件
2) 子进程调用 setsid 创建会话
这步是关键,在子进程中调用 setsid()函数创建新的会话,由于之前子进程并不是进程组的组长进程,所以调用 setsid()会使得子进程创建一个新的会话,子进程成为新会话的首领进程,同样也创建了新的进程组、子进程成为组长进程,此时创建的会话将没有控制终端。 所以这里调用 setsid 有三个作用:让子进程摆脱原会话的控制、让子进程摆脱原进程组的控制和让子进程摆脱原控制终端的控制
注:在调用 fork 函数时,子进程继承了父进程的会话、进程组、控制终端等,虽然父进程退出了,但原先的会话期、进程组、控制终端等并没有改变,因此,那还不是真正意义上使两者独立开来。 setsid 函数能够使子进程完全独立出来,从而脱离所有其他进程的控制。
3) 将工作目录更改为根目录
子进程是继承了父进程的当前工作目录,由于在进程运行中,当前目录所在的文件系统是不能卸载的,这对以后使用会造成很多的麻烦。因此通常的做法是让“/”作为守护进程的当前目录,当然也可以指定其它目录来作为守护进程的工作目录

4) 重设文件权限掩码 umask

由于使用 fork 函数新建的子进程继承了父进程的文件权限掩码,这就给子进程使用文件带来了诸多的麻烦。因此, 把文件权限掩码设置为 0, 确保子进程有最大操作权限、这样可以大大增强该守护进程的灵活性。设置文件权限掩码的函数是 umask,通常的使用方法为 umask(0)。

5) 关闭不再需要的文件描述符
子进程继承了父进程的所有文件描述符, 这些被打开的文件可能永远不会被守护进程(此时守护进程指的就是子进程,父进程退出、子进程成为守护进程) 读或写,但它们一样消耗系统资源,可能导致所在的文件系统无法卸载,所以必须关闭这些文件, 这使得守护进程不再持有从其父进程继承过来的任何文件描述符。

6) 将文件描述符号为 0、 1、 2 定位到/dev/null
将守护进程的标准输入、标准输出以及标准错误重定向到/dev/null,这使得守护进程的输出无处显示、也无处从交互式用户那里接收输入。
7) 其它:忽略 SIGCHLD 信号
处理 SIGCHLD 信号不是必须的, 但对于某些进程,特别是并发服务器进程往往是特别重要的,服务器进程在接收到客户端请求时会创建子进程去处理该请求,如果子进程结束之后,父进程没有去 wait 回收子进程,则子进程将成为僵尸进程;如果父进程 wait 等待子进程退出,将又会增加父进程的负担、也就是增加服务器的负担,影响服务器进程的并发性能,在 Linux 下,可以将 SIGCHLD 信号的处理方式设置为SIG_IGN,也就是忽略该信号,可让内核将僵尸进程转交给 init 进程去处理,这样既不会产生僵尸进程、又省去了服务器进程回收子进程所占用的时间。

18、SIGHUP 信号

当用户准备退出会话时, 系统向该会话发出 SIGHUP 信号,会话将 SIGHUP 信号发送给所有子进程,子进程接收到 SIGHUP 信号后,便会自动终止,当所有会话中的所有进程都退出时,会话也就终止了; 因为程序当中一般不会对 SIGHUP 信号进行处理,所以对应的处理方式为系统默认方式, SIGHUP 信号的系统默认处理方式便是终止进程。

linux C编程11-进程相关推荐

  1. 【Linux系统编程】进程替换:exec 函数族

    00. 目录 文章目录 00. 目录 01. exec函数族 02. 参考示例 2.1 execl函数示例 2.2 execv函数示例 2.3 execlp() 或 execvp()函数示例 2.4 ...

  2. vbs结束进程代码_物联网学习教程—Linux系统编程之进程控制

    Linux系统编程之进程控制 一.结束进程 首先,我们回顾一下 C 语言中 continue, break, return 的作用: continue: 结束本次循环 break: 跳出整个循环,或跳 ...

  3. Linux系统编程之进程与线程控制原语对比

    Linux系统编程之进程与线程控制原语对比 进程 线程 fork pthread_create exit pthread_exit wait pthread_join kill pthread_can ...

  4. 【Linux系统编程】进程概述和进程号

    00. 目录 文章目录 00. 目录 01. 进程概述 02. 进程状态 03. 进程控制块 04. 进程号 05. 进程号相关函数 06. 案例实战 07. 附录 01. 进程概述 我们平时写的 C ...

  5. linux系统编程之进程(八):守护进程详解及创建,daemon()使用

    linux系统编程之进程(八):守护进程详解及创建,daemon()使用 一,守护进程概述 Linux Daemon(守护进程)是运行在后台的一种特殊进程.它独立于控制终端并且周期性地执行某种任务或等 ...

  6. 【Linux系统编程】进程退出和回收进程资源

    00. 目录 文章目录 00. 目录 01. 进程退出函数 02. 进程退出讨论 03. 回收进程资源 04. 附录 01. 进程退出函数 #include <stdlib.h>void ...

  7. linux 进程 控制终端,linux系统编程之进程(五):终端、作业控制与守护进程

    #include#define ERR_EXIT(m) do { perror(m); exit(EXIT_FAILURE); } while(0) int setup_daemon(int, int ...

  8. 【Linux系统编程】进程内存模型

    00. 目录 文章目录 00. 目录 01. Linux可执行程序结构 02. Linux进程结构 03. 存储类型总结 04. 附录 01. Linux可执行程序结构 在 Linux 下,程序是一个 ...

  9. linux系统编程之进程概念(操作系统---管理,进程创建,进程状态,进程优先级, 环境变量,程序地址空间,进程O(1)调度方法)

    系统编程: 进程概念->进程控制->基础IO->进程间通信->进程信号->多线程 进程概念 冯诺依曼体系结构----现代计算机硬件体系结构 冯诺依曼体系结构----现代计 ...

  10. 【Linux系统编程】进程介绍

    进程 我们平时写的 C 语言代码,通过编译器编译,最终它会成为一个可执行程序,当这个可执行程序运行起来后(没有结束之前),它就成为了一个进程. 程序是存放在存储介质上的一个可执行文件,而进程是程序执行 ...

最新文章

  1. github充当服务器_如何创建充当链接HTML按钮
  2. SDN商用落地:遍地开花不代表全面实现
  3. 使用SpringMVC创建支持向下兼容的版本化的API接口
  4. python rgb 图像_在Python中查找RGB图像的互补图像
  5. Android ScrollView内部组件设置android:layout_height=fill_parent无效的解决办法
  6. PostgreSQL中定时job执行(pgAgent)
  7. 网易云音乐被纳入港股通 3月7日起生效
  8. 《OSPF网络设计解决方案(第2版)》一1.4 TCP/IP协议簇
  9. C语言基础入门实例汇总(共65个案例)
  10. 光伏发电系统最大功率点跟踪MPPT matlab/simulink仿真 扰动观察法
  11. 主流百兆交换机芯片介绍
  12. VoLTE业务端到端流程:EPC侧信令流程
  13. php自动生成模板文件,Laravel学习笔记之Artisan命令生成自定义模板的方法
  14. 第四周作业part1
  15. 百度ueditor编辑器控制图片在编辑框中的大小
  16. 40 岁的时候,我转行成为一名前端开发者!
  17. DigiCert和GlobalSign单域名OV SSL证书对比评测
  18. pc_lint的用法转
  19. 债券基础知识和可转债剖析
  20. js几行代码搞定html转图片制作海报,html2canvas应用实例

热门文章

  1. STL算法——内建函数对象
  2. 计算机一级打字体大字母怎么打,键盘打字母出现数字,小编教你电脑键盘打字母出现数字怎么办...
  3. android设计模式书籍,耗时两个礼拜,8000字Android面试长文,真香!
  4. 未来5年中国十大吸金行业:IT互联网排名第一
  5. word格式刷如何连续刷多段不同字段?
  6. C语言 生辰八字+五行+纳音 查询
  7. 原型制作软件Axure RP 9函数集合(较全面……)
  8. nginx——SSL证书安全模块
  9. java-php-net-python-房产交易资金管理系统计算机毕业设计程序
  10. 变形金刚3 ——结尾擎天柱经典独白