异常控制流之fork()

本文详细探讨了fork()函数在程序中的使用,通过示例解析了逻辑运算符与fork()结合时的流程,强调了缓存问题及并发执行的特点。文章分析了不同阶段的fork流程,并介绍了wait()、waitpid()等函数在处理子进程结束时的作用,同时提到了进程间通信和信号处理。此外,还讨论了CTRL-C和CTRL-Z在中断和挂起进程方面的区别,并给出了家庭作业。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

t# fork与逻辑运算

#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc,char *argv[])
{
	fork();
	fork()&&fork()||fork();
	fork();
	printf("main\n");
}

./try
try流程图
1.A&&B,若A = 0,则不会执行B。
A||B,若A != 0,则不会执行B。
2.特别提示:A&&B||C,当A=0时仅仅不执行B,但还会执行C。
3.如图所示,为fork()&&fork()||fork()的流程图。
由于fork()一次调用,两次返回的特性,无条件限制时fork()乘2,在此代码中表达式之前,之后各有一个fork(),故一共有5*4=20个程 序,除去原始的一个,则还余下新建的19个。

缓存的问题

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
 
int main(void)
{
   int i;
   for(i=0; i<2; i++){
      fork();
      printf("-");
   }
   wait(NULL);
   wait(NULL);
   return 0;
}

./buff
1.会打印8个“-”,因为没有\n,所以不会直接刷新缓存输出到设备上,在fork时,子进程会复制到父进程的缓存内容,导致多输出两个“-”。

main函数:

/*
 * forks.c - Examples of Unix process control
 */
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h> 
#include <signal.h>

int main(int argc, char *argv[])
{
    int option = 0;
    if (argc > 1)
	option = atoi(argv[1]);
    switch(option) {
    case 0: fork0();
	break;
    case 1: fork1();
	break;
    case 2: fork2();
	break;
    case 3: fork3();
	break;
    case 4: fork4();
	break;
    case 5: fork5();
	break;
    case 6: fork6();
	break;
    case 7: fork7();
	break;
    case 8: fork8();
	break;
    case 9: fork9();
	break;
    case 10: fork10();
	break;
    case 11: fork11();
	break;
    case 12: fork12();
	break;
    case 13: fork13();
	break;
    case 14: fork14();
	break;
    case 15: fork15();
	break;
    case 16: fork16();
	break;
    case 17: fork17();
	break;
    default:
	printf("Unknown option %d\n", option);
	break;
    }
    return 0;
}

fork0

/*
 * fork0 - The simplest fork example
 * Call once, return twice
 * Creates child that is identical to parent
 * Returns 0 to child process
 * Returns child PID to parent process
 */
void fork0() 
{
    if (fork() == 0) {
	printf("Hello from child\n");
    }
    else {
	printf("Hello from parent\n");
    }
}

./fork0
1.调用一次,返回两次:fork函数被父进程调用一次,返回的是新创建的子进程和父进程。
2.并发:父进程和子进程独立运行,结果显示在我的系统上为父进程先完成printf,输出Hello from parent,子进程后完成printf,输出 Hello from child,但是在另外的系统上可能正好相反。
进程图如下所示:
fork0流程图

fork1

/* 
 * fork1 - Simple fork example 
 * Parent and child both run same code
 * Child starts with identical private state
 */
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);
}

./fork1

1.fork()创建子进程时会获得父进程的代码和数据,故子进程中的x初始值也=1。
2.\n 换行并在输出时刷新缓存。
3.getpid()函数:获取当前进程号。
4.进程号:子进程的id号在父进程之后一个。
5.由于并发故可能出现类似顺序:
Parent has x = 0
Child has x=2
Bye from process 23080 with x = 0
Bye from process 23081 with x = 2
不回跳即可。
流程图如下:
fork1流程图

fork2

 /* fork2 - Two consecutive forks
 * Both parent and child can continue forking
 * Ordering undetermined
 */
void fork2()
{
    printf("L0\n");
    fork();
    printf("L1\n");    
    fork();
    printf("Bye\n");
}

./fork2
流程图如下:
fork2流程图

fork3

/*
 * fork3 - Three consective forks
 * Parent and child can continue forking
 */
void fork3()
{
    printf("L0\n");
    fork();
    printf("L1\n");    
    fork();
    printf("L2\n");    
    fork();
    printf("Bye\n");
}

./fork3

流程图如下:fork3流程图

fork4

/* 
 * fork4 - Nested forks in parents
 */
void fork4()
{
    printf("L0\n");
    if (fork() != 0) {
		printf("L1\n");    
		if (fork() != 0) {
	  	  printf("L2\n");
		}
    }
    printf("Bye\n");
}

./fork4
特别提示:编程规范——首行缩进;注意作用域,不要犯低级错误

![fork4流程图

fork5

/*
 * fork5 - Nested forks in children
 */
void fork5()
{
    printf("L0\n");
    if (fork() == 0) {
		printf("L1\n");    
		if (fork() == 0) {
	  	  printf("L2\n");
		}
    }
    printf("Bye\n");
}

./fork5
类似fork4
流程图如下:
./fork5

fork6

void cleanup(void) {
    printf("Cleaning up\n");
}
/*
 * fork6 - Exit system call terminates process
 * call once, return never
 */
void fork6()
{
    atexit(cleanup);
    fork();
    exit(0);
}

./fork6
atexit() : 注册终止函数,即完成各种清除操作,函数原型为void atexit(void (*func)(void));
atexit()函数调用时机:(1-5:正常;6-8:异常)
1)从main函数返回;
2) 调⽤exit函数;
3) 调⽤_exit或_Exit;
4)最后⼀个线程从启动例程返回;
5) 最后⼀个线程调⽤pthread_exit;
6) 调⽤abort函数;
7) 接到⼀个信号并终⽌;
8) 最后⼀个线程对取消请求做出响应调⽤exit函数;
我们这里用了exit,故出现了两次Cleaning up!
(atexit函数的调用顺序是和登记顺序相反的)

fork7

/* 
 * fork7 - Demonstration of zombies.
 * Run in background and then perform ps 
 */
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 */
    }
}

./fork7
1.一个进程终止时,内核并不是立即把它清除掉而是让进程保持在一种已终止的状态,直到它的父进程被回收,结合代码,可以看出,父进程中有一个无限循环,导致父进程迟迟不能被回收,故我们运行时程序会陷入一个死循环。
2.CTRL-Z和CTRL-C都是中断命令但是作用不同:
CTRL-C是中断。
CTRL-Z是挂起,还可以继续前台或后台的任务。
(fg命令重新启动前台被中断的任务,bg命令把被中断的任务放在后台执行)

fork8

/* 
 * fork8 - Demonstration of nonterminating child.  
 * Child still running even though parent terminated
 * Must kill explicitly
 */
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);
    }
}

./fork8
不会出现上述fork7的问题,关闭终端时也不会有提示kill。
印证了两个进程独立的特性。

fork9

/*
 * fork9 - synchronizing with and reaping children (wait)
 */
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");
}

./fork9
1.wait()函数原型:int wait(int *status)
2.功能:父进程一旦调用了wait就立即阻塞自己,如果让它找到变成僵尸的子进程,wait就会收集这个子进程的信息,正常情况下wait()的返回值为子进程的PID(异常:wait()的返回值则为-1)并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞,直到出现为止。
故运行结果才出现HC在前,CT在后的情况。

fork10

#define N 5
/* 
 * fork10 - Synchronizing with multiple children (wait)
 * Reaps children in arbitrary order
 * WIFEXITED and WEXITSTATUS to get info about terminated children
 */
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);
    }
}

./fork10
1.WIFEXITED():这个宏用来指出子进程是否为正常退出的,如果正常退出则返回一个非零值。
2.WEXITSTATUS():当WIFEXITED返回子进程的返回值,看子进程调用的exit()的参数,在此代码中子进程调用exit(100), exit(101)exit(102)exit(103)exit(104),故WEXITSTATUS(status)返回100,101,102,103,104
3.由于exit倒序收回,故输出为104,103,102,101,100。
4.pid_t:自定义类型,进程id 的类型。

fork11

/* 
 * fork11 - Using waitpid to reap specific children
 * Reaps children in reverse order
 */
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);
    }
}
/********* 
 * Signals
 *********/

./fork11
waitpid():
函数原型:pid_t waitpid(pid_t pid,int *status,int options)
功能:让父进程知道某一个自己创建的子进程何时结束。
参数说明:
1)pid_t pid
父进程欲等待的子进程识别码,其具体含义如下:
pid<-1 等待进程组号为pid绝对值的任何子进程。
pid=-1 等待任何子进程,此时的waitpid()函数就退化成了普通的wait()函数。
pid=0 等待进程组号与目前进程相同的任何子进程,也就是说任何和调用waitpid()函数的进程在同一个进程组的进程。
pid>0 等待进程号为pid的子进程。
2)int *status
该参数将保存子进程的状态信息。如果status不是空指针,则状态信息将被写入,如果不关心子进程的状态的话,可以传入空指针。
3)int options
提供了一些另外控制waitpid()函数的行为的选项来。如果不想使用这些选项,则可以把这个参数设为0。
waitpid不按照特定的顺序等待他的所有子进程终止。

fork12

/*
 * fork12 - Sending signals with the kill() function
 */
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);
    }
}

./fork12
1.kill():
函数原型:int kill(pid_t pid, int sig);
功能:用于向任何进程组或进程发送信号。
2.此代码中的kill(pid[i], SIGINT);
pid大于零时,pid是信号欲送往的进程的标识。
SIGINT程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl-C)时发出,用于通知前台进程组终止进程。
以此种方式终止了无限循环的子进程。
3.最后一个循环,显示了子进程的终止情况,同样,是创建顺序的倒序终止。

fork13

/*
 int_handler - SIGINT handler
 */
void int_handler(int sig)
{
    printf("Process %d received signal %d\n", getpid(), sig); /* Unsafe */
    exit(0);
}
/*
 * fork13 - Simple signal handler example
 */
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);
    }
}

./fork13

1.signal()
功能: 设置某一信号的对应动作。
原型: sighandler_t signal(int signum, sighandler_t handler)然后退出。
signum:指明了所要处理的信号类型。  
handler:描述了与信号关联的动作。
2.此代码中 signal(SIGINT, int_handler);
SIGINT:由Interrupt Key产生,通常是CTRL+C或者DELETE。
int_handler:关联的动作为输出Process 子进程ID received signal 信号
3.整个流程就是:终止程序,输出对应动作,然后退出。

fork14

/*
 * child_handler - SIGCHLD handler that reaps one terminated child
 */
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 */
}

/*
 * fork14 - Signal funkiness: Pending signals are not queued
 */
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)
	;
}

./fork14
1.fflush(stdout)
stdout是行缓冲的,输出先放在一个buffer里面,换行时输出到屏幕。
fflush(stdout)刷新标准输出缓冲区,把输出缓冲区里的东西打印到标准输出设备上。
2.signal(SIGCHLD, child_handler);
SIGCHLD 进程Terminate或Stop的时候,SIGCHLD会发送给它的父进程。缺省情况下该Signal会被忽略。
child_handlel指定的动作,本代码中输出Received SIGCHLD signal 信号 for process 进程 ,并刷新到输出设备上。
3.sleep(1);
linux下的sleep函数原型为: unsigned int sleep(unsigned int seconds);
sleep(n)单位为秒。
功能:将目前动作暂停指定的时间。
4. child_handler只回收一个故可以看到只回收了一个子进程。

fork15

/*
 * child_handler2 - SIGCHLD handler that reaps all terminated children
 */
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 */
    }
}
/*
 * fork15 - Using a handler that reaps multiple children
 */
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();
    }
}

./fork15
1.pause();
使调用进程暂停,直到接收到信号,要么终止,或导致它调用一个信号捕获函数。
2.child_handlel指定的动作,本代码中输出Received signal 信号 from process 进程 ,并刷新到输出设备上。
3.child_handler2中,阻塞返回子进程的状态,正常终止都有动作,故回收多个进程。

fork16

/* 
 * fork16 - Demonstration of using /bin/kill program 
 */
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);
    }
} 
 

./fork16
1.getpid():返回当前进程标识。
2.getpgrp():函数返回进程ID的进程组,如果未指定,则返回当前进程组。

fork17

/* 
 * Demonstration of using ctrl-c and ctrl-z 
 */
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);
} 

./forks17
1.CTRL-Z和CTRL-C都是中断命令但是作用不同。
CTRL-C是中断。
CTRL-Z是挂起,还可以继续前台或后台的任务。

家庭作业

8.11

#include<stdio.h>
int main()
{
	int i;
	for(i=0;i<2;i++)
		fork();
	printf("hello\n");
	exit(0);
}

在这里插入图片描述
8.12

#include<stdio.h>
void doit()
{
	fork();
	fork();
	printf("hello\n");
	return;
}

int main()
{
	doit();
	printf("hello\n");
	exit(0);
}

在这里插入图片描述
8.13

#include<stdio.h>
int main()
{
	int x=3;
	if(fork()!=0)
		printf("x=%d\n",++x);
	printf("x=%d\n",--x);
	exit(0);
}

在这里插入图片描述
8.14

#include<stdio.h>
void doit()
{
	if(fork()==0){
		fork();
		printf("hello\n");
		exit(0);
	}
	return;
}

int main()
{
	doit();
	printf("hello\n");
	exit(0);
}

在这里插入图片描述
8.15

#include<stdio.h>
void doit()
{
	if(fork()==0){
		fork();
		printf("hello\n");
		return;
	}
	return;
}

int main()
{
	doit();
	printf("hello\n");
	exit(0);
}

在这里插入图片描述
注意return是返回调用的主函数,而exit是退出程序。

8.16

#include<stdio.h>
#include<sys/wait.h>
int counter=1;
int main()
{
	if(fork()==0){
	counter--;
	exit(0);
	}
	else{
	wait(NULL);
	printf("counter=%d\n",++counter);
	}
	exit(0);
}

在这里插入图片描述
8.18

#include<stdio.h>
void end(void)
{
	printf("2");
	fflush(stdout);
}
int main()
{
	if(fork()==0)
		atexit(end);
	if(fork()==0){
		printf("0");fflush(stdout);
	}
	else{
		printf("1");fflush(stdout);
	}
	exit(0);
}

在这里插入图片描述
012021

8_21

#include<stdio.h>
#include<sys/wait.h>
int main()
{
	if(fork()==0){
		printf("a"); fflush(stdout);
	exit(0);
	}
	else{
		printf("b"); fflush(stdout);
		waitpid(-1,NULL,0);
	}
	printf("c");fflush(stdout);
	exit(0);
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值