17个fork的介绍

一.什么是fork?

  1. fork是Linux x86-64系统中常用的系统调用的实例,意为创建进程
  2. 进程是,简而言之就是运行中的程序。
  3. 使用fork创建进程时,若返回值为0则为子进程,反之为父进程。
  4. fork创建进程时有两个特点,其一为调用一次,返回两次,即一次是返回到父进程,一次是返回到新创建的子进程。其二是父进程与子进程是并发执行的,即二者是并发运行的独立进程。以上两个特点就导致了返回值的不确定,可能先执行父进程,也有可能先执行子进程。

二. forks的讲解
1.fork0()


f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ gcc forks.c
f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ ./a.out 0 &
[1] 2786
f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ Hello from parent
Hello from child
/*
 * 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");
    }
}

在这里插入图片描述

创建一个进程,若返回值为0,则表示是孩子在讲话。输出Hello from child,反之则输出Hello from parent.由于父进程与子进程是并发执行的,所以也可能出现先输出Hello from parent,再输出Hello from child.
2.fork1()

f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ ./a.out 1 &
[1] 2795 //父进程的id号
f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ Parent has x = 0
Bye from process 2795 with x = 0
Child has x = 2
Bye from process 2796 with x = 2
/* 
 * 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);
}

在这里插入图片描述
在这里先初始化一个int类型的数x为1,接着fork一下,若为孩子,则x变为2,若为父亲,则x变为0.此时要注意父进程与子进程是并发执行的,相互独立的,所以x是不会相互影响,它们只是对自己的x进行了操作。getpid()指的是获得当前进程的pid。
3.fork2()

f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ ./a.out 2 &
[1] 2803
f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ L0
L1
Bye
Bye
L1
Bye
Bye
/*
 * 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");
}

在这里插入图片描述
首先输出一个L0,接着fork()产生子进程,在这个程序中并没有用任何的判断条件,所以不管是子进程,还是父进程,都是可以输出L1,Bye的。举个例子就是,没有限制,人人都有份。你有我有全都有
4.fork3()

f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ ./a.out 3 &
[1] 2824
f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ L0
L1
L2
Bye
Bye
L2
Bye
L1
L2
Bye
L2
Bye
Bye
Bye
Bye
/*
 * 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()和fork2()很类似,,就是像2的倍数一样增加输出。详情参见fork2().
注:由于父,子进程的并发性,运行结果不唯一,取决于机器的运行情况。
5.fork4()

f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ ./a.out 4 &
[1] 2832
f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ L0
L1
L2
Bye
Bye
Bye


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

在这里插入图片描述

这里已经不是人人都有份了,一开始的L0是大家所共有的,接着,来了一个fork()产生了子进程,这个父亲比较厉害,它自己享有一份L1,让孩子就say Bye,后来父亲又有了二胎, 父亲 又努力赚钱了,但是还是自己享有了一份L2,让第二个孩子也say Bye了。所以我们可以看到在父进程这里有四个输出,而孩子只有一个Bye.
6.fork5()

f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ ./a.out 5 &
[1] 2836
f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ L0
Bye
L1
Bye
L2
Bye
/*
 * 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");
}

在这里插入图片描述
刚说完勤奋赚钱的父亲,转眼间我们又看到了努力向上的好孩子,同样的一开始的L0也是共有的,但是等孩子长大了又能力,就自己赚得了一份L1,但是却没有孝敬给父亲,父亲只能say Bye.后来孩子生了个孩子,孙子同样也很有能力,能够自己赚到一份L2,但是依旧延续了上一代的习俗,没有孝敬给父亲,父亲就直接say Bye了。所以在上面我们可以看到每一个孩子都有一份自己赚得的L#,而父亲只有Bye.
7.fork6()

f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ ./a.out 6 &
[1] 2839
f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ Cleaning up
Cleaning up

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

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

在这里插入图片描述

C 库函数 int atexit(void (*func)(void)) 当程序正常终止时,调用指定的函数 func(在程序终止时被调用的函数)。您可以在任何地方注册你的终止函数,但它会在程序终止的时候被调用。

首先就来了个atexit(cleanup),这个呢是表示在在程序终止之后调用括号内的函数,因此在fork()创建进程,并且exit(0)之后会调用cleanup函数,输出Cleaning up.
8.fork7()

f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ ./a.out 7 &
[1] 2841
f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ Running Parent, PID = 2841
Terminating Child, PID = 2842
/* 
 * 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 */
    }
}

在这里插入图片描述
在这里,fork()创建进程,若为孩子,则输出Terminating Child,然后退出程序,走向死亡,而父进程在输出Running Parent之后还在不停的循环,导致孩子的尸体没有人去收拾,孩子进程变成了僵尸进程。
9.fork8()

f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ ./a.out 8 &
[2] 2843
f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ Terminating Parent, PID = 2843
Running Child, PID = 2844
/* 
 * 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);
    }
}

在这里插入图片描述
前面的孩子进程很可怜,成为了僵尸进程,现在的这个孩子与之可谓是同样悲惨,fork()产生进程,孩子在输出一句话之后就开始埋头苦干,而父进程在说完遗言后就匆匆离世,孩子进程变成了孤儿进程,也不能够被父进程回收。
10.fork9()

f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ ./a.out 9 &
[2] 2845
f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ HP: hello from parent
HC: hello from child
CT: child has terminated
Bye
/*
 * 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");
}

在这里插入图片描述
fork()产生进程,如果是子进程,就输出Hello之后就exit(0),若为父进程,在输出Hello之后要等待子进程退出之后才能够输出CT,最后输出Bye。而子进程已经退出了,所以不再输出Bye。
11.fork10()

f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ ./a.out 10 &
[2] 2847
f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ Child 2852 terminated with exit status 104
Child 2851 terminated with exit status 103
Child 2850 terminated with exit status 102
Child 2849 terminated with exit status 101
Child 2848 terminated with exit status 100
/* 
 * fork10 - Synchronizing with multiple children (wait)
 * Reaps children in arbitrary order
 * WIFEXITED and WEXITSTATUS to get info about terminated children
 */
 #define N 5
 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);
    }
}

在这里插入图片描述
首先运行for循环,若为孩子进程,则exit(100+i)的形式退出,接着执行下一个for循环,等待孩子退出之后,判断孩子是否是正常退出的,若正常退出,则输出Child %d terminated with exit status %d\n类似这样的话,因为孩子进程都是以exit()退出,所以均为正常退出。注:在这里的输出结果是不一定会按照顺序的,因为等待的是所有的孩子。
12.fork11()

f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ ./a.out 11 &
[2] 2853
f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ Child 2858 terminated with exit status 104
Child 2857 terminated with exit status 103
Child 2856 terminated with exit status 102
Child 2855 terminated with exit status 101
Child 2854 terminated with exit status 100
/* 
 * 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);
    }
}

fork11()的进程图与fork10()的进程图类似。
这个与上一个的区别在于:

fork10():
pid_t wpid = wait(&child_status);
fork11():
pid_t wpid = waitpid(pid[i], &child_status, 0);

在上一个,任意一个子进程结束后都会执行for循环里面的内容,而这里使用了waitpid打破了这种随机性,只有相应进程号的子进程结束后才会执行for循环内的代码,所以此处的输出一定会按照顺序。
13.fork12()

f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ ./a.out 12 &
[2] 2862
f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ Killing process 2863
Killing process 2864
Killing process 2865
Killing process 2866
Killing process 2867
Child 2867 terminated abnormally
Child 2866 terminated abnormally
Child 2865 terminated abnormally
Child 2864 terminated abnormally
Child 2863 terminated abnormally
/*
 * 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);
    }
}

在这里插入图片描述
一开始执行for循环内部的代码,孩子进程应该不停的循环的,但是后续的for循环使孩子死于非命,甚至发送了一个信号SIGINT,而父进程在等待孩子死亡之后输出了哪个孩子被杀害了。因为是死于非命,故输出为else里面的内容。但是因为没有指定特定的孩子,所以输出也不会按顺序,取决于当前机器的运行情况。
14.fork13()

f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ ./a.out 13 &
[2] 2868
f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ Killing process 2869
Killing process 2870
Killing process 2871
Killing process 2872
Killing process 2873
Process 2873 received signal 2
Child 2873 terminated with exit status 0
Process 2872 received signal 2
Child 2872 terminated with exit status 0
Process 2871 received signal 2
Child 2871 terminated with exit status 0
Process 2870 received signal 2
Child 2870 terminated with exit status 0
Process 2869 received signal 2
Child 2869 terminated with exit status 0
/*
 * fork13 - Simple signal handler example
 */
 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);
    }
}

在这里插入图片描述
在这里先执行for循环里面的代码,同样,孩子进程应该不停的进行循环,但是被杀了,并且发送了一个信号,与前面不同的地方是有相应的处理信号的函数,在函数的最后是exit(0)表示是正常退出了,所以执行的是孩子进程正常退出后的if里面的语句。而且SIGINT在信号表中值为2。
15.fork14()

f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ ./a.out 14 &
[2] 2874
f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ Received SIGCHLD signal 17 for process 2875
注:会输出一到五行
/*
 * fork14 - Signal funkiness: Pending signals are not queued
 */
 
/*
 * 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 */
}
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)
	;
}

在这里插入图片描述
在这里孩子进程先休眠了1秒钟之后才退出,而且发送了一个信号SIGCHLD,SIGCHLD在信号表中的值为17,一个类型的信号至多只会有一个待处理信号,而当已经有孩子发送SIGCHLD信号时,其他的信号就不会被接收,就会变成僵死进程。会输出一到五行是因为可能孩子进程休眠完发送信号,部分或整体被接收到了。
16.fork15()

f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ ./a.out 15 &
[3] 2880
f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ Received signal 17 from process 2881
Received signal 17 from process 2882
Received signal 17 from process 2883
Received signal 17 from process 2884
Received signal 17 from process 2885

/*
 * fork15 - Using a handler that reaps multiple children
 */
 /*
 * 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 */ //清空缓冲区
    }
}
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()比fork14()多增加的地方:

while (ccount > 0) {
	pause();
    }
while ((pid = wait(&child_status)) > 0) {
	ccount--;
	printf("Received signal %d from process %d\n", sig, pid); /* Unsafe */
	fflush(stdout); /* Unsafe */ //清空缓冲区
    }

在这里面使用了pause()函数,目的是让父进程在执行死循环时,暂停一会,此时只要接收到一个信号就可以就可以转到处理信号的函数里面把所有已经结束了的孩子进程的id号打印出来。
17.fork16()

f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ ./a.out 16 &
[3] 2886
f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ Child1: pid=2887 pgrp=2886
Child2: pid=2888 pgrp=2886
/* 
 * 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);
    }
} 

在这里插入图片描述
fork()创建一个进程,若为孩子进程,则输出孩子进程的id号和组号,孩子进程的组号一般为父亲进程的id号,接着再fork()产生进程,同样为孩子才能打出pid与组号,其他的就会死循环,此时我们可以用kill -9来杀死相应的进程。
18.fork17()

f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ ./a.out 17 &
[3] 2889
f@f-virtual-machine:/mnt/hgfs/test_code/chap8_code$ Parent: pid=2889 pgrp=2889
Child: pid=2890 pgrp=2889

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

在这里插入图片描述
fork()产生进程,若为孩子,则输出孩子的pid和组号,反之输出父亲的,在完成输出后,父,子进程都会死循环。而此时使用Ctrl+c会直接终止程序,使用ps可以发现死循环的僵死进程已经被清除了,而使用Ctrl+z则是挂起,通过ps可以发现僵死进程还在,需要用kill -9 来杀死。

好啦,以上就是所有内容啦,感谢观看!

socketpair是一组用于创建全双工通信管道的函数。它可以在内核中创建一对已连接的套接字,这对套接字之间可以进行双向通信,类似于管道。 socketpair函数的原型如下: ```c #include <sys/types.h> #include <sys/socket.h> int socketpair(int domain, int type, int protocol, int sv[2]); ``` 参数说明: - domain:地址族,可以是AF_INET(IPv4)、AF_INET6(IPv6)或AF_UNIX(本地套接字)。 - type:套接字类型,可以是SOCK_STREAM(流式套接字)或SOCK_DGRAM(数据报套接字)。 - protocol:协议类型,一般为0,表示自动选择。 - sv:指向套接字文件描述符的数组,其中sv[0]表示第一个套接字,sv[1]表示第二个套接字。 使用socketpair函数创建套接字对后,可以使用fork函数来创建子进程,子进程可以通过套接字与父进程通信,实现进程间通信。 下面是一个简单的例子,演示了socketpair函数的使用: ```c #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> int main() { int sv[2]; char buf[1024]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) { perror("socketpair"); return -1; } if (fork() == 0) { // 子进程 close(sv[0]); write(sv[1], "hello from child", 16); read(sv[1], buf, sizeof(buf)); printf("%s\n", buf); } else { // 父进程 close(sv[1]); write(sv[0], "hello from parent", 17); read(sv[0], buf, sizeof(buf)); printf("%s\n", buf); } return 0; } ``` 该例子中创建了一个AF_UNIX域的套接字对,其中父进程向子进程发送了一条消息并等待子进程的回复,子进程读取了父进程发送的消息并回复了一条消息给父进程。运行该程序可以看到如下输出: ``` hello from child hello from parent ``` 可以看到子进程和父进程之间通过socketpair函数创建的套接字进行了双向通信。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值