深入理解计算机系统--fork函数
fork函数
一.函数的解析
(1) fork:创建进程
(2) fork函数调用一次,返回两次。返回的两次一次是在父进程中,fork返回子进程的PID;一次是在子进程中,fork返回0。其中,子进程的PID总是为0,其余大于0的数都为父进程的PID,由此返回值就提供一个明确的方法来分辨程序是在父进程还是子进程中进行的。PID就是进程ID,每个进程都有一个唯一的正数(非零)进程ID。
二.fork函数例子解析
1.所举例子的头文件代码
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
- unistd.h为Linux/Unix系统中内置头文件,包含了许多系统服务的函数原型,例如read函数、write函数和getpid函数等。其作用相当于windows操作系统的"windows.h",是操作系统为用户提供的统一API接口,方便调用系统提供的一些服务。(此头文件解析来自博主原创文章,原文链接为https://blog.csdn.net/weixin_43154187/article/details/90311026)
- sys/types.h:基本系统数据类型,此头文件还包含适当时应使用的多个基本派生类型。所有这些类型在 ILP32 编译环境中保持为32 位值,并会在 LP64 编译环境中增长为 64 位值。
类型
caddr_t 核心地址。clock_t 表示系统时间(以时钟周期为单位)。comp_t 压缩的时钟滴答。dev_t 用于设备号。fd_set 文件描述集。fpos_t 文件位置。gid_t 数组值ID。ino_t i节点编号。off_t 用于文件大小和偏移量。mode_t 文件类型,文件创建模式。pid_t 进程ID和进程组IDptrdiff_t 是一种带符号整型,用于对两个指针执行减法运算后所得的结果。rlim_t 资源限制;size_t 反映内存中对象的大小(以字节为单位)。ssize_t 供返回字节计数或错误提示的函数使用。time_t 以秒为单位计时。uid_t 数值用户ID。wchar_t 能表示所有不同的字符码。
(此头文件解析来自其他博主转载的博客,因找不到原文链接,此处放上转载文章的博主ID:MyAnqi)
- sys/wait.h:在使用wait()函数和waitpid()函数时需要定义这个头文件。
- signal.h:程序执行中报告的不同信号的处理。
2.例子1
(1)源代码
void fork0()
{if (fork() == 0) {printf("Hello from child\n");}else {printf("Hello from parent\n");}
}
(2)进程图
(3)在Linux环境下的运行结果
3.例子2
(1)源代码
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);
}
(2)进程图
(3)在Linux环境下的运行结果
4.例子3
(1)源代码
void fork2()
{printf("L0\n");fork();printf("L1\n"); fork();printf("Bye\n");
}
(2)进程图
(3)在Linux环境下运行的结果
5.例子4
(1)源代码
void fork3()
{printf("L0\n");fork();printf("L1\n"); fork();printf("L2\n"); fork();printf("Bye\n");
}
(2)进程图
(3)在Linux环境下的运行结果
6.例子5
(1)源代码
void fork4()
{printf("L0\n");if (fork() != 0) {printf("L1\n"); if (fork() != 0) {printf("L2\n");}}printf("Bye\n");
}
(2)进程图
(3)在Linux环境下的运行结果
7.例子6
(1)源代码
void fork5()
{printf("L0\n");if (fork() == 0) {printf("L1\n"); if (fork() == 0) {printf("L2\n");}}printf("Bye\n");
}
(2)进程图
(3)在Linux环境下的运行结果
8.例子7
(1)源代码
void cleanup(void) {printf("Cleaning up\n");
}
void fork6()
{atexit(cleanup);fork();exit(0);
}
- atexit()函数:
a.函数声明:int atexit(void (*func)(void)); atexit()函数的参数是一个函数指针,函数指针指向一个没有参数也没有返回值的函数。
b.函数解析:atexit()函数用来注册程序正常终止(也就是通过exit(0)、_exit(0)或return结束的程序)时要被调用的函数。
(此函数解析参考原创文章,文章链接为:https://blog.csdn.net/tf_apologize/article/details/53162218)
(2)在Linux环境下的运行结果
9.例子8
(1)源代码
void fork7()
{if (fork() == 0) {/* Child */printf("Terminating Child, PID = %d\n", getpid());exit(0);} else {printf("Running Parent, PID = %d\n", getpid());while (1); /* Infinite loop */}
}
(2)进程图
(3)在Linux环境下的运行结果
(4)函数解析
- 其运行结果是无法自动结束的。因为子进程在printf之后执行exit函数,所以子进程终止。但是父进程在printf后执行while(1),此循环是无法结束的,所以子进程终止后父进程无法终止,导致shell无法出来。此时若想结束进程,只能进行强制性终止。(这里用ctrl+z挂起,其中还有ctrl+c终止)
- 用ps命令查看进程状态,执行ps命令可以查看哪些进程正在运行及其运行的状态、进程是否结束、进程有没有僵尸、进程占用的资源等等
执行ps命令运行结果
- 相关符号的解释:
PID为进程的id号
TTY为登入者的终端机位置
TIME为使用掉的CPU时间
CMD为所下达的指令名称
defunct为僵尸进程 - 由运行结果知,PID为3292的进程为僵尸进程。
- 僵尸进程:如果子进程死亡时父进程没有wait()或者waitpid(),就会产生僵尸进程,且子进程将永远保持这样直到父进程 wait()。通常用 ps 可以看到它被显示为“”。
- 僵尸进程的危害:如果父进程不调用wait / waitpid的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统的进程表容量是有限的,所能使用的进程号也是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程。所以,defunct进程不仅占用系统的内存资源,影响系统的性能,而且如果其数目太多,还会导致系统瘫痪。而且,由于调度程序无法选中Defunct进程,所以不能用kill命令删除Defunct 进程,惟一的方法只有重启系统。
(关于Defunct的解析及僵尸进程的解释参考原创博文,文章链接:http://hanover.iteye.com/blog/881972)
10.例子9
(1)源代码
void fork8()
{if (fork() == 0) {/* Child */printf("Running Child, PID = %d\n",getpid());while (1); /* Infinite loop */} else {printf("Terminating Parent, PID = %d\n",getpid());exit(0);}
}
(2)进程图
(3)在Linux环境下的运行结果
(4)函数解析:fork8相对于fork7,就是将while(1)的循环调到子进程后面,将exit(0)调到父进程后面,此时在我的机器上运行是不会产生僵尸进程的。其ps后查看进程状态的结果如下:
11.例子10
(1)源代码
void fork9()
{int child_status;if (fork() == 0) {printf("HC: hello from child\n");exit(0);} else {printf("HP: hello from parent\n");wait(&child_status);printf("CT: child has terminated\n");}printf("Bye\n");
}
(2)进程图
(3)在Linux环境下的运行结果
(4)函数解析:
- 由运行结果知,执行完父进程的第一个printf后遇到wait(),转移去执行子进程,子进程执行结束后返回继续执行父进程,直至结束。
- wait()函数:
原型:pid_t wait(int *statusp)
返回:成功返回子进程ID,出错返回-1
作用:等待子进程退出并回收,防止僵尸进程产生
12.例子11
(1)源代码
void fork10()
{pid_t pid[N];int i, child_status;for (i = 0; i < N; i++)if ((pid[i] = fork()) == 0) {exit(100+i); /* Child */}for (i = 0; i < N; i++) { /* Parent */pid_t wpid = wait(&child_status);if (WIFEXITED(child_status))printf("Child %d terminated with exit status %d\n",wpid, WEXITSTATUS(child_status));elseprintf("Child %d terminate abnormally\n", wpid);}
}
(2)在Linux环境下的运行结果
(3)函数解析
- WIFEXITED(status):指出子进程是否为正常退出,如果是,则返回一个非零值。
- WEXITSTATUS(status):当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS(status)就会返回5;如果子进程调用exit(7),WEXITSTATUS(status)就会返回7。请注意,如果进程不是正常退出的,也就是说,WIFEXITED返回0,这个值就毫无意义。
(此处两个宏的解析均来自CSDN博主[阅微草堂ZSF]的原创文章,文章链接为:https://blog.csdn.net/zhengshifeng123/article/details/52639778)
13.例子12
(1)源代码
void fork11()
{pid_t pid[N];int i;int child_status;for (i = 0; i < N; i++)if ((pid[i] = fork()) == 0)exit(100+i); /* Child */for (i = N-1; i >= 0; i--) {pid_t wpid = waitpid(pid[i], &child_status, 0);if (WIFEXITED(child_status))printf("Child %d terminated with exit status %d\n",wpid, WEXITSTATUS(child_status));elseprintf("Child %d terminate abnormally\n", wpid);}
}
(2)在Linux环境下的运行结果
14.例子13
(1)源代码
#define N 5
void fork12()
{pid_t pid[N];int i;int child_status;for (i = 0; i < N; i++)if ((pid[i] = fork()) == 0) {/* Child: Infinite Loop */while(1);}for (i = 0; i < N; i++) {printf("Killing process %d\n", pid[i]);kill(pid[i], SIGINT);}for (i = 0; i < N; i++) {pid_t wpid = wait(&child_status);if (WIFEXITED(child_status))printf("Child %d terminated with exit status %d\n",wpid, WEXITSTATUS(child_status));elseprintf("Child %d terminated abnormally\n", wpid);}
}
(2)在Linux环境下的运行结果
15.例子14
(1)源代码
void int_handler(int sig)
{printf("Process %d received signal %d\n", getpid(), sig); /* Unsafe */exit(0);
}
void fork13()
{pid_t pid[N];int i;int child_status;signal(SIGINT, int_handler);for (i = 0; i < N; i++)if ((pid[i] = fork()) == 0) {/* Child: Infinite Loop */while(1);}for (i = 0; i < N; i++) {printf("Killing process %d\n", pid[i]);kill(pid[i], SIGINT);}for (i = 0; i < N; i++) {pid_t wpid = wait(&child_status);if (WIFEXITED(child_status))printf("Child %d terminated with exit status %d\n",wpid, WEXITSTATUS(child_status));elseprintf("Child %d terminated abnormally\n", wpid);}
}
(2)在Linux环境下的运行结果
(3)函数解析
- signal函数:
#include <signal.h>
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler);
signum指信号名称;
sighandler_t handler指接收到信号之后转而去执行的程序。
- 常用的Linux信号
2 SIGINT:来自键盘的中断。
9 SIGKILL:杀死程序。
14 SIGALRM:来自alarm函数的定时器信号。
17 SIGCHLD:一个子进程停止或终止。
16.例子15
(1)源代码
int ccount = 0;
void child_handler(int sig)
{int child_status;pid_t pid = wait(&child_status);ccount--;printf("Received SIGCHLD signal %d for process %d\n", sig, pid); /* Unsafe */fflush(stdout); /* Unsafe */
}
void fork14()
{pid_t pid[N];int i;ccount = N;signal(SIGCHLD, child_handler);for (i = 0; i < N; i++) {if ((pid[i] = fork()) == 0) {sleep(1);exit(0); /* Child: Exit */}}while (ccount > 0);
}
(2)在Linux环境下的运行结果
(3)函数解析
- 运行结果shell命令无法显示,ctrl+z挂起后,执行ps命令查看进程状态,存在4个僵尸进程。结果如下:
17.例子16
(1)源代码
void child_handler2(int sig)
{int child_status;pid_t pid;while ((pid = wait(&child_status)) > 0) {ccount--;printf("Received signal %d from process %d\n", sig, pid); /* Unsafe */fflush(stdout); /* Unsafe */}
}
void fork15()
{pid_t pid[N];int i;ccount = N;signal(SIGCHLD, child_handler2);for (i = 0; i < N; i++)if ((pid[i] = fork()) == 0) {sleep(1);exit(0); /* Child: Exit */}while (ccount > 0) {pause();}
}
(2)在Linux环境下的运行结果
18.例子17
(1)源代码
void fork16()
{if (fork() == 0) {printf("Child1: pid=%d pgrp=%d\n",getpid(), getpgrp());if (fork() == 0)printf("Child2: pid=%d pgrp=%d\n",getpid(), getpgrp());while(1);}
}
(2)在Linux环境下的运行结果
(3)函数解析
- getpgrp()函数:用来取得目前进程所属的组识别码,相当于调用getpid()。
18.例子17
(1)源代码
void fork17()
{if (fork() == 0) {printf("Child: pid=%d pgrp=%d\n",getpid(), getpgrp());}else {printf("Parent: pid=%d pgrp=%d\n",getpid(), getpgrp());}while(1);
}
(2)运行结果
此文章参考《深入理解计算机系统》一书及网上资料而写,为了方便大家更好的理解和运用fork()函数而写,主要是通过举例说明,希望可以帮助到跟我一样的人,如有哪些地方写的不够好,请多指教!
深入理解计算机系统--fork函数相关推荐
- 关于理解Perl的fork函数的一个范例
方便理解,Perl的fork函数派生子进程的过程: #!/usr/bin/perl -w # wangxiaoyu#live.com use strict; defined(my $pid=fork( ...
- 浅显理解*nix下的守护进程机制及fork函数
最近空闲时间重新仔细看了一下memcached的使用说明文档,硬着头皮看了一点源码,有时候看到一些晦涩的c函数感觉实在恍惚只能跳过.不过也不算是全无收获,终于LZ还敢再看c语言,终于LZ又看起了c语言 ...
- 理解进程、通过调用 fork 函数创建进程
文章目录 1.理解进程 1.1 CPU核的个数与进程数 1.2 进程 ID 2.通过调用 fork 函数创建进程 1.理解进程 进程(Process),其定义如下:"占用内存空间的正在运行的 ...
- Unix/Linux fork()函数的理解
作者:王姗姗,华清远见嵌入式学院讲师. 对于刚刚接触Unix/Linux操作系统,在Linux下编写多进程的人来说,fork是最难理解的概念之一:它执行一次却返回两个值. 首先我们来看下fork函数的 ...
- os.fork()函数理解
os.fork()函数官方文档解释. fork是分叉的意思.根据官方文档我的理解是从主进程执行到fork函数部分开始分叉执行.(fork函数创建了一个子进程与主进程一起在cpu内执行.) os.for ...
- 深入理解计算机系统9个重点笔记
引言 深入理解计算机系统,对我来说是部大块头.说实话,我没有从头到尾完完整整的全部看完,而是选择性的看了一些我自认为重要的或感兴趣的章节,也从中获益良多,看清楚了计算机系统的一些本质东西或原理性的内容 ...
- 计算机系统性错误,《深入理解计算机系统-异常》
现代操作系统通过使控制流发生突变来对某些意外情况(磁盘读写数据准备就绪.硬件定时器产生信号等)做出反应.一般而言,我们把这些突变命名为异常控制流(Exceptional Contral Flow EC ...
- HIT深入理解计算机系统大作业
计算机系统 大作业 题 目 程序人生-Hello's P2P 专 业 计算机 学 号 120L021909 班 级 2003006 学 生 邢俊文 指 导 ...
- 《深入理解计算机系统(原书第三版)》pdf
下载地址:网盘下载 内容简介 · · · · · · 和第2版相比,本版内容上*大的变化是,从以IA32和x86-64为基础转变为完全以x86-64为基础.主要更新如下: 基于x86-64,大量地重 ...
最新文章
- python信用卡违约_Python信用卡验证
- Python安装时我遇到的一些问题
- Python 使用sys.exc_info自己捕获异常详细信息
- 应用开发者必须了解的Kubernetes网络二三事
- Python学习笔记:Day 7 编写MVC
- Spring实战 MethodInvokingJobDetailFactoryBean使用与分析
- Oracle数据库基本概念理解(2)
- android蓝牙pair,Android向更多蓝牙设备开放Fast Pair功能 配对更轻松了
- PowerDNS管理工具开发中学习到的DNS知识
- 【转】使用 OpenSSL API 进行安全编程 - 创建基本的安全连接和非安全连接
- 湖北 政府项目 软件 测试,湖北电子政务应用系统技术验收测试规范.doc
- 【雷达】毫米波雷达和激光雷达
- 【STM32F407的DSP教程】第17章 DSP功能函数-定点数互转
- php显示某年某月某日,输入某年某月某日,判断这一天是这一年的第几天?
- 穿越六年艰难转型,明道云终于再获主流投资
- 使用U盘win10家庭版本系统重装
- Python中while循环练习——打印星星总结
- 读《Android群英传》的一些感想
- 2万字雄文:饿了么核心交易系统 5 年演化史!
- python-day18(正式学习)