深入理解计算机系统--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和进程组ID

ptrdiff_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));
 else
     printf("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));
 else
     printf("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));
 else
     printf("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));
 else
     printf("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()函数而写,主要是通过举例说明,希望可以帮助到跟我一样的人,如有哪些地方写的不够好,请多指教!
  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值