基本必备知识

fork函数的基本说明

介绍

fork函数用于创建一个新的进程,下面引用一段话介绍一下:

一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己

函数原型

pid_t fork( void);

其中pid_t是一个宏定义,其本身是int型的数据。

函数的返回

fork函数调用非常的神奇,一次调用将会有两次放回,一次是在父进程之中返回,另一次是在子进程里面返回。
而父进程的的返回又分为两种情况:

  • 返回值大于零:成功创建子进程并返回子进程的PID
  • 返回值为-1:发生错误,创建子进程失败
  • 返回值为0:子进程的返回值是0

wait函数与waitpid函数的基本说明

waitpid函数

waitpid()会暂时停止目前进程的执行,直到有信号来到或子进程结束.

函数原型

pid_t waitpid(pid_t pid, int *statusp, int options);
  1. pid所等待的子进程
    pid参数有种情况:

    • pid > 0只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
    • pid = 0waitpid所等待的进程是同一个进程组里的任意一个子进程。
    • pid = -1wait所等待的进程是父进程的任意一个子进程。
    • pid < -1waitpid所等待的进程是一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。
  2. statusp回收的子进程的退出状态
    • 如果statusp是一个空指针,则表示父进程不关心子进程结束的状态。
    • 如果statusp不是一个空指针,则会在status放上关于导致返回的子进程的状态信息,相关信息请看下面的表格
宏(返回值) 信息
WIFEXITED(status) 如果子进程通过调用exit或者一个返回(returm)正常终止,就返回真。
WEXITSTATUS(status) 返回一个正常终止的子进程的退出状态。
WIFSIGNALED(status) 如果子进程是因为一个未被捕获的信号终止的,那么就返回真。
WTERMSIG(status) 返回导致子进程终止的信号的编号。
WIFSTOPPED(status) 如果引起返回的子进程当前是停止的,那么就返回真。
WSTOPSIG(status) 返回引起子进程停止的信号的编号。
WIFCONTINUED(status) 如果子进程收到SIGCONT信号重新启动,则返回真。
  1. options默认行为
    可以通过将options设置为常量WNOHANGWUNTRACEDWCONTINUED或是它们的组合来修改默认行为。
常量 行为
WNOHANG 如果等待集合中的任何子进程都还没有终止,那么就立即返回(返回值为0)。默认的行为是挂起调用进程,直到有子进程终止。在等待子进程终止的同时,如果还想做些有用的工作,这个选项会有用。
WUNTRACED 挂起调用进程的执行,直到等待集合中的一个进程变成已终止或者被停止。返回的PID为导致返回的已终止或被停止子进程的PID。默认的行为是只返回已终止的子进程。当你想要检查已终止和被停止的子进程时,这个选项会有用。
WCONTINUED 挂起调用进程的执行,直到等待集合中一个正在运行的进程终止或等待集合中一个被停止的进程收到SIGCONT信号重新开始执行。

函数的返回

  • 如过成功则返回子进程的 PID
  • 如果参数optionsWNOHANG则返回 0
  • 如果发生了其他的错误则会返回 -1

wait函数

函数原型

pid_t wait(int *status);

其实调用wait(&status)等价于调用waitpid(- 1, &status, 0 ),这里就不对这个函数做过多的解释了。

函数的返回

  • 如过成功则返回子进程的 PID
  • 如果发生了错误则会返回 -1

signal函数的基本说明

函数的声明

void (*signal(int sig, void (*func)(int)))(int);

signal函数用于修改信号与其相关联的默认行为,例如接收到SIGKILL信号默认行为就是终止接收进程,接收到SIGCHLD信号默认行为就是忽略这个信号。有一点特殊的是,SIGSTOPSIGKILL,这两个的信号的默认行为是无法修改的。

参数

  • sig:待修改关联的信号
  • func:修改关联信号的新处理函数

fork函数的使用例子

例子1

代码:

void fork0()
{if (fork() == 0) {printf("Hello from child,pid = %d\n",getpid());}else {printf("Hello from parent,pid = %d\n",getpid());}
}

它输出的内容是:

Hello from parent,pid = 18913
Hello from child,pid = 18914

fork函数的介绍里面有说过,fork函数是一次调用两次返回,我们就可以通过在父进程与子进程中返回的内容的不同来分辨当前进程是父进程还是子进程,从而达到我们所需要的效果。
fork函数在子进程之中返回 0,所以Hello from child,pid = 18914应是由子进程在完成 if 条件语句后转跳到printf函数进行输出。而父进程中fork函数返回的是子进程的pid(18914),则Hello from parent,pid = 18913应是由父进程进行输出。

例子2

代码:

void fork1()
{int x = 1;pid_t pid = fork();if (pid == 0) {printf("Child has x = %d\n", ++x);} else {printf("Parent has x = %d\n", --x);}printf("Bye from process %d with x = %d\n", getpid(), x);
}

输出到屏幕的内容如下:

hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ ./f 1
Parent has x = 0
Child has x = 2
Bye from process 16 with x = 0
Bye from process 17 with x = 2

父进程调用fork函数创建了一个新的进程活后,此时系统之中就会出现了两个基本一样的进程,无论是代码或是数据都基本一样,就相当于把父进程复制了一份,但由于fork函数返回的值不同,会有相应的逻辑判断,从而导致基本一样的进程执行的代码却不太一样。

例子3

代码:

void fork3()
{printf("L0\n");fork();printf("L1\n");    fork();printf("L2\n");    fork();printf("Bye\n");
}

输出到屏幕的内容如下:

hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ ./f 3
L0 from process 18
L1 from process 18
L1 from process 19
L2 from process 18
L2 from process 20
L2 from process 19
L2 from process 21
Bye from process 18
Bye from process 22
Bye from process 20
Bye from process 23
Bye from process 19
Bye from process 24
Bye from process 21
Bye from process 25

主要是理解了fork函数的使用后,弄清楚它的逻辑,既能够很清楚的理解它的输出为什么是这样。当然,不同的机器跑出来的结果有可能会不一样,个别内容的输出顺序可能会跟我上面的不一样,这得看CPU如何处理这些进程。
下面是一个大致的流程图:

例子4

代码:

void fork5()
{printf("L0 from process %d,ppid:%d\n", getpid(),getppid());if (fork() == 0) {printf("L1 from process %d,ppid:%d\n", getpid(),getppid());    if (fork() == 0) {printf("L2 from process %d,ppid:%d\n", getpid(),getppid());}}printf("Bye from process %d,ppid:%d\n", getpid(),getppid());
}

输出到屏幕的内容如下:

hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ ./f 5
L0 from process 38,ppid:4
Bye from process 38,ppid:4
L1 from process 39,ppid:38
Bye from process 39,ppid:1
L2 from process 40,ppid:39
Bye from process 40,ppid:1

下面是一个大致的流程图:

例子5

代码:

void fork18()
{printf("fork");if (fork() == 0){printf("2018\n");}printf("2019\n");
}

你觉得这个代码所输出的内容会是什么呢?
输出到屏幕的内容如下:

hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ ./f 18
fork2019
fork2018
2019

其实这printf的缓冲机制有关,printf并不会直接把内容直接打印到屏幕上,而是放在了缓存之中,所以在执行fork的时候,缓冲区的内容也会被放到子进程相应的地方。一般来说,缓冲区满后或是遇到了换行符才会把内容输出到屏幕上。

例子6

代码:

 void fork9_handler(int sig)
{printf("Process %d received signal %d\n", getpid(), sig);exit(0);
}
void fork9()
{int child_status;signal(SIGINT,fork9_handler);if (fork() == 0) {printf("HC: hello from child\n");while(1);} else {printf("HP: hello from parent\n");wait(&child_status);printf("CT: child has terminated\n");}printf("Bye\n");
}

由于在子进程中有死循环,所以该程序无法正常的结束,可以另一个终端窗口发送SIGINT信号给子进程令程序正常结束。所以输出入下:

hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ ./f 9
HP: hello from parent
HC: hello from child
Process 90 received signal 2
CT: child has terminated
Bye

但是我在运行上面的程序时,在子进程死循环时挂起进程,再发送SIGINT信号给子进程,然而此时父进程却不能够正常的退出了:

hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ ./f 9
HP: hello from parent
HC: hello from child
^Z
[1]+  已停止               ./f 9
hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ psPID TTY          TIME CMD4 tty1     00:00:00 bash17 tty1     00:00:00 f18 tty1     00:00:01 f19 tty1     00:00:00 ps
hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ kill -2 18
Process 18 received signal 2
hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ psPID TTY          TIME CMD4 tty1     00:00:00 bash17 tty1     00:00:00 f18 tty1     00:00:01 f <defunct>20 tty1     00:00:00 ps
hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ fg 1
./f 9
^Z
[1]+  已停止               ./f 9
hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ psPID TTY          TIME CMD4 tty1     00:00:00 bash17 tty1     00:00:00 f18 tty1     00:00:01 f <defunct>21 tty1     00:00:00 ps

且在另一个终端查看进程时仍能够看到父进程还在运行着,但是拿着同样的程序在另外一台机器上跑确是能够正常的退出,或许这是Windows子系统在某方面没做好吧。
于是我又再次修改了代码,进行实验:

 void fork9_handler(int sig)
{printf("Process %d received signal %d\n", getpid(), sig);exit(0);
}
void fork9()
{int child_status;signal(SIGCHLD, fork9_handler); //Addsignal(SIGINT,fork9_handler);if (fork() == 0) {printf("HC: hello from child\n");while(1);} else {printf("HP: hello from parent\n");wait(&child_status);printf("CT: child has terminated\n");}printf("Bye\n");
}

运行结果如下:

hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ ./f 9
HP: hello from parent
HC: hello from child
^Z
[1]+  已停止               ./f 9
hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ psPID TTY          TIME CMD4 tty1     00:00:00 bash16 tty1     00:00:00 f17 tty1     00:00:01 f18 tty1     00:00:00 ps
hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ kill -2 17
Process 17 received signal 2
hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ fg 1
./f 9
Process 16 received signal 17
hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ psPID TTY          TIME CMD4 tty1     00:00:00 bash19 tty1     00:00:00 ps

这一次父进程能够正常的退出了,所以我个人怀疑是wait函数在Window子系统下会发生一些未知的错误。


参考资料

  • wait()和waitpid()的参数解析
  • waitpid_百度百科
  • Computer Systems: A Programmer’s Perspective
  • fork()函数详解
  • Signal ()函数用法和总结

关于fork函数的使用相关推荐

  1. fork是linux函数吗,linux fork()函数

    概述 最近在看进程间的通信,看到了fork()函数,虽然以前用过,这次经过思考加深了理解.现总结如下: 1.函数本身 (1)头文件 #include #include (2)函数原型 pid_t fo ...

  2. Linux基础学习系列:对于fork()函数的学习,及进程创建相关知识

    fork()函数 :由当前进程再生成一个进程出来 #include <sys/types.h> #include <unistdh> pid_t fork(void); 返回: ...

  3. 深入解析Linux中的fork函数

    1.定义 #include <unistd.h> #include<sys/types.h> pid_t fork( void ); pid_t 是一个宏定义,其实质是int, ...

  4. fork()函数_fork()函数的使用

    fork的意思是个叉子,在unix及其衍生版Linux中,用于创建子进程,现在看一下fork函数的基本用法. #include<stdio.h> #include<stdlib.h& ...

  5. linux中的fork函数详解

    在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程.在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID.我们可以通过fork返回的值来 ...

  6. Linux下fork()函数

    Linux下的fork()函数是系统调用不是C语言内置的库函数,这里mark一下笔试面试中常出现的fork()试题. 程序一: #include "stdio.h" #includ ...

  7. 关于理解Perl的fork函数的一个范例

    方便理解,Perl的fork函数派生子进程的过程: #!/usr/bin/perl -w # wangxiaoyu#live.com use strict; defined(my $pid=fork( ...

  8. linux中fork()函数与vfork()函数的区别

    对于fork函数: 子进程只继承父进程的文件描述表,不继承但共享文件表项和i-node 父进程创建一个子进程之后,文件表项中的引用计数加1变为2,当父进程作close操作之后计数器减1,子进程还是可以 ...

  9. linux创建进程fork函数和vfork函数

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

  10. linux源码阅读笔记 fork函数

    在阅读源码的过程中,发现找不到fork函数的定义.后来在linux/init/main.c中找到了这样一条语句 static inline _syscall0(int,fork) 原来这里就是fork ...

最新文章

  1. python画有权重网络图_python networkx 根据图的权重画图实现
  2. scvmm2008R2创建委派管理员角色
  3. vs code golang插件记录
  4. 验证字符串是否以指定字符开头
  5. 【软考-软件设计师】计算机存储系统
  6. java spring注解_spring注解是如何实现的?
  7. 热胀冷缩,但为什么水结冰体积会膨胀?
  8. 安卓程序员都懂:如何用Espresso对UI界面测试?
  9. ORA-12518: TNS: 监听程序无法分发客户机连接
  10. Transaction marked as rollbackOnly异常处理 Duplicate entry 'xxx' for key
  11. ansible 简单使用
  12. 安卓Service完全解析(上)
  13. JAVA程序设计:在圆内随机生成点(LeetCode:478)
  14. 第十六章 二次根式 教案
  15. 【工程光学】光度学色度学
  16. linux修改密码点点点root,linux系统批量修改root用户密码
  17. 深挖阿里健康财报中的隐藏剧情:慢病管理布局已成?
  18. 【Teradata】windows部署安装Teradata数据库(附虚拟机扩展包)
  19. plotly.js 常见图形使用 常见图形操作 折线图 热力图 轮廓图 泡泡图 图点击事件
  20. VMware vSphere Hypervisor 7(ESXi 7)

热门文章

  1. 固态硬盘故障检测_diskgenius检测固态硬盘(ssd固态硬盘坏道修复)
  2. sha256算法_以太坊2.0将弃用Keccak256,而启用SHA256哈希算法?
  3. 黑苹果安装教程,配EFI引导文件+软件!
  4. 深信服 云桌面 linux,深信服桌面云-深信服桌面云下载 v3.0官方版--pc6下载站
  5. AXURE 8.1.0.3382 有效激活码
  6. 《数据分析思维手册》和《数据分析师的职场真相》全集整理好啦,下载保存!...
  7. tongweb使用之端口冲突处理办法
  8. 苹果笔记本安装windows系统
  9. 苹果鼠标驱动_EFI引导目录drivers64UEFI文件夹(.efi)文件驱动介绍
  10. Matlab画图函数与参数