计算机系统基础学习 技术日志二(fork函数)

本文详细介绍了计算机系统中的fork函数,通过代码示例、运行结果和流程图展示了fork函数创建子进程的过程。文章涵盖了从fork的基本概念到多级fork的应用,以及与kill函数、信号处理等相关知识的结合,深入解析了进程的并发执行和资源副本特性。
摘要由CSDN通过智能技术生成

fork函数综述

  1. 一个已有的进程通过调用fork函数来创建一个新进程。新创建的进程称为子进程(child),而原来的进程则称为父进程(parent)。
  2. 子进程一个获得父进程代码、数据段、堆、栈等资源的副本。子进程获得与父进程的虚拟地址空间相同的副本,但是父子进程间不共享这些存储空间,子进程有了独立的地址空间。
  3. 父子进程最大的区别在于PID(进程标识符)的不同。
  4. 子进程被创建时,系统中有两个活动的进程:父进程和子进程。它们并发执行;调用一次,返回两次。

下面是对一些fork函数的实际操作和理解:
主要通过代码段,运行结果和流程图来展示学习过程
要用到的一些头文件,下面就不一一列出来了:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h> 
#include <signal.h>

fork0

代码:

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

运行结果:

Hello from parent
Hello from child

流程图与分析:
在这里插入图片描述
调用fork(),创建子进程,fork() == 0时,子进程输出Hello from child;
fork()返回值不为0,父进程输出Hello from parent。
父子进程并发执行,输出的顺序不固定。

fork1

代码:

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);
}

结果:

Parent has x = 0
Bye from process 14609 with x = 0]
Child has x = 2
Bye from process 14610 with x = 2

流程图:
在这里插入图片描述
x=1,调用fork(),创建子进程,
当fork() == 0时,即fork()返回值为0,子进程获得父进程的副本。x++,故x = 2,输出pid和x。
当fork()返回值不为0,父进程中,子进程对x的运算是独立的,x–,所以x=0。

fork2

代码:

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

结果:

L0
L1
Bye
Bye
L1
Bye
Bye

流程图:
在这里插入图片描述

fork3

代码:

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

结果:

L0
L1
L2
Bye
Bye
L2
Bye
L1
L2
Bye
Bye
Bye
L2
Bye
Bye

流程图:
与fork2基本相同,只是多了一次fork,使最后输出8次Bye。

fork4

代码:

void fork4()
{
    printf("L0\n");
    if (fork() != 0) {
    printf("L1\n");    
    if (fork() != 0) {
        printf("L2\n");
        }
    }
    printf("Bye\n");
}

结果:

L0
L1
L2
Bye
Bye
Bye

流程图:
在这里插入图片描述
fork()进行了判定

fork5

代码:

void fork5()
{
    printf("L0\n");
    if (fork() == 0) {
    printf("L1\n");    
    if (fork() == 0) {
        printf("L2\n");
        }
    }
    printf("Bye\n");
}

void cleanup(void) {
    printf("Cleaning up\n");
}

结果:

L0
Bye
L1
Bye
L2
Bye

流程图:
与fork4基本相同

fork6

代码:

void fork6()
{
    atexit(cleanup);
    fork();
    exit(0);
}

结果:

Cleaning up
Cleaning up

流程图:
在这里插入图片描述atexit()在函数终止时被调用,故在fork()后执行,父进程和子进程都会输出Cleaning up。

fork7

代码:

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 */
    }
}

结果:

Running Parent, PID = 15225
Terminating Child, PID = 15226
^Z
[8]+ 已停止 ./fork 7

流程图:
在这里插入图片描述
子进程输出语句结束,父进程输出语句后一直进行while循环。Shell命令不会执行,需要手动挂起或中断父进程

fork8

代码:

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);
    }
}

结果:

Running Child, PID = 15229
Terminating Parent, PID = 15228

流程图:
与fork7基本相同,只是子进程与父进程输出调换,子进程终止,父进程不断循环,需要手动挂起或终止。

fork9

代码:

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");
}

结果:

HP: hello from parent
HC: hello from child
CT: child has terminated
Bye

流程图:
在这里插入图片描述

fork10

代码:

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);
    }
}

结果:

Child 14873 terminated with exit status 100
Child 14874 terminated with exit status 101
Child 14875 terminated with exit status 102
Child 14877 terminated with exit status 104
Child 14876 terminated with exit status 103

分析:
WIFEXITED(status)如果子进程正常结束则为非0值。
WEXITSTATUS(status)取得子进程exit()返回的结束代码,一般会先用WIFEXITED 来判断是否正常结束才能使用此宏。
因为一个for循环,循环了五次,每次的fork返回一个子进程一个父进程,子进程调用exit直接退出,父进程每次调用wait返回子进程的状态,当结束时调用输出语句,输出多少进程号的子进程结束,并伴有状态码。

fork11

代码:

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);
    }
}

结果:

Child 14883 terminated with exit status 104
Child 14882 terminated with exit status 103
Child 14881 terminated with exit status 102
Child 14880 terminated with exit status 101
Child 14879 terminated with exit status 100

分析:
与fork10不同的是,父进程的循环是反着的,所以输出子进程的进程号也是从大到小输出的。

kill函数

头文件用法:
#include <sys/types.h>
#include <signal.h>
定义:
int kill(pid_t pid, int sig);
功能:
发送指定的信号到相应进程。
不指定型号将发送SIGTERM(15)终止指定进程。如果任无法终止该程序可用“-kill” 参数,其发送的信号为SIGKILL(9) ,将强制结束进程,使用ps命令或者jobs 命令可以查看进程号。root用户将影响用户的进程,非root用户只能影响自己的进程。

fork12

代码:

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);
      }
}

结果:

Killing process 15245
Killing process 15246
Killing process 15247
Killing process 15248
Killing process 15249
Child 15246 terminated abnormally
Child 15249 terminated abnormally
Child 15247 terminated abnormally
Child 15245 terminated abnormally
Child 15248 terminated abnormally

分析:
fork12子进程中有一个死循环,即一直运行,父进程中杀死了子进程,输出的时候wait获得子进程的状态,因为不是exit正常退出而是被杀死的,因此输出子进程不正常的结束。

fork13

代码:

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);
    }
}

结果:

Killing process 15186
Killing process 15187
Killing process 15188
Killing process 15189
Killing process 15190
Process 15188 received signal 2
Process 15189 received signal 2
Child 15189 terminated with exit status 0
Child 15188 terminated with exit status 0
Process 15190 received signal 2
Process 15187 received signal 2
Process 15186 received signal 2
Child 15186 terminated with exit status 0
Child 15187 terminated with exit status 0
Child 15190 terminated with exit status 0

分析:
signal的参数:
SIGINT (信号中断)交互式注意信号。通常由应用程序用户生成。
父进程调用kill时发出信号2,即进程中断,触发处理信号函数 int_handler,结束子进程,最后输出的结果也是正常退出。

fork14

代码:

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)
 ;
}

结果:

Received SIGCHLD signal 17 for process 15207
^Z
[2]+ 已停止 ./fork14

分析:
pause() :它会令目前的进程挂起, 直到信号被传送,进程要么终止要么触发调用信号处理函数。
返回值:当捕获信号并且信号捕获函数返回时才返回。这种情况下,仅返回-1,并且errno设置为EINTR
sleep() :sleep()会令目前的进程暂停参数seconds 所指定的时间, 或是暂停中途被信号所中断.
返回值:若进程暂停到参数seconds 所指定的时间则返回0, 若有信号中断则返回剩余秒数.

fork15

代码:

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();
    }
}

结果:

Received signal 17 from process 15199
Received signal 17 from process 15200
Received signal 17 from process 15201
Received signal 17 from process 15202
Received signal 17 from process 15203

分析:

在死循环中使用pause函数让父进程被挂起,只有捕获到信号的时候 触发信号处理程序,这个时候子进程运行完,被挂起的父进程收到了子进程返回的状态信号,因此,触发了输出语句

fork16

代码:

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);
    }
} 

结果:

Child1: pid=15213 pgrp=15212
Child2: pid=15214 pgrp=15212

分析:
getpgrp():用来取得目前进程所属的组识别码. 函数相当于调用getpgid(0),嵌套if中,只有子进程进入,之后子进程中又调用了fork,同样只有子进程输出语句

fork17

代码:

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);
}

结果:

Parent: pid=15215 pgrp=15215
Child: pid=15216 pgrp=15215
^Z
[3]+ 已停止 ./fork 17

分析:
doit函数中,子程序最后调用exit直接退出,则不会继续执行输出语句,而父进程不会进if,则执行return返回到main函数,执行之后的输出语句,后exit结束进程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值