ECF入门(上)

fork()指令

One call,Two return.返回值若为0,说明当前在子进程中;返回值大于0,说明在父进程中,返回值为其子进程的PID

getpid()

返回当前进程的ID

waitpid(pid_t pid,int *statusp,int options)

  • pid:

 pid>0时只等待进程ID为pid的子进程结束

 pid=-1时等待其所有的子进程中的任何一个(只要一个)结束

  • options:

 options=0(默认情况)时,挂起父进程,等待其子进程结束。返回子进程编号。

 options=WNOHANG时,父进程不挂起。如果一个子进程都没有结束的话,返回0;否则返回子进程编号。

  • 如果调用函数的进程没有子进程,waitpid返回-1,errno设为ECHILD

wait(&status)

等价于waitpid(-1,&status,0)

WIFEXITED(status):

如果子进程是以exit或者return正常退出的,函数返回值就为true

WEXITSTATUS(status)

前提是WIFEXITED一定为true,此函数返回正常终止的子进程的退出状态,即exit的值

atexit()

在进程结束调用exit时,调用atexit()括号中的注册函数,注册几次就调用几次。并且它的调用顺序和登记顺序是相反的。与压栈顺序有关。

相关习题


  • CSAPP书上部分

preparation:

1.http://csapp.cs.cmu.edu/public/code.html 下载.tar文件解压,包含了CSAPP书上的所有代码

2.运行sudo mv csapp.h /usr/include和sudo mv csapp.c /usr/include ,然后再usr/include中修改csapp.h在#endif 之前添加 #include"csapp.c"(我用的vi)

3.在gcc的时候最后加上-lpthread就可以

关于下列代码中用到的Fork和Wait在csapp.c中的定义:

/* $begin forkwrapper */
pid_t Fork(void)
{
    
    
    pid_t pid;

    if ((pid = fork()) < 0)
        unix_error("Fork error");
    return pid;
}
/* $end forkwrapper */

/* $begin wait */
pid_t Wait(int *status)
{
    
    
    pid_t pid;

    if ((pid  = wait(status)) < 0)
        unix_error("Wait error");
    return pid;
}
/* $end wait */

*8.11这个程序会输出多少个"hello"输出行?

扫描二维码关注公众号,回复: 12424946 查看本文章
#include "csapp.h"
int main()
{
    
    
	int i;
	
	for(i=0;i<2;++i)
		Fork();
	printf("hello\n");
	exit(0);
}

运行结果:
在这里插入图片描述
一共四行
进程图:在这里插入图片描述
*8.12这个程序会输出多少个hello输出行?

#include "csapp.h"
void doit()
{
    
    
    Fork();
    Fork();
    printf("hello\n");
    return;
}

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

在这里插入图片描述总共8行hello
进程图:
在这里插入图片描述
*8.13下面程序的一种可能的输出是什么?

#include "csapp.h"

int main()
{
    
    
    int x = 3;

    if (Fork() != 0)
	printf("x=%d\n", ++x);

    printf("x=%d\n", --x);
    exit(0);
}

运行结果:
在这里插入图片描述
在这里插入图片描述
可能的结果应该还有
在这里插入图片描述
在这里插入图片描述
*8.14下面这个程序会输出多少个"hello"输出行?

/* $begin forkprob5 */
#include "csapp.h"

void doit() 
{
    
    
    if (Fork() == 0) {
    
    
	Fork();
	printf("hello\n");
	exit(0);
    }
    return;
}

int main() 
{
    
    
    doit();
    printf("hello\n");
    exit(0);
}
/* $end forkprob5 */

在这里插入图片描述
一共3个hello
进程图:
在这里插入图片描述
*8.15下面这个程序会输出多少个"hello"输出行?

/* $begin forkprob5 */
#include "csapp.h"

void doit() 
{
    
    
    if (Fork() == 0) {
    
    
	Fork();
	printf("hello\n");
	return;
    }
    return;
}

int main() 
{
    
    
    doit();
    printf("hello\n");
    exit(0);
}
/* $end forkprob5 */

运行结果:
在这里插入图片描述
一共五个hello
进程图:
在这里插入图片描述
*8.16下面这个程序的输出是什么?

/* $begin forkprob7 */
#include "csapp.h"
int counter = 1;

int main() 
{
    
    
    if (fork() == 0) {
    
    
	counter--;  
	exit(0);
    }
    else {
    
    
	Wait(NULL); 
	printf("counter = %d\n", ++counter);
    }
    exit(0);
}
/* $end forkprob7 */

运行结果:
在这里插入图片描述
进程图:
在这里插入图片描述

**8.18考虑下面程序可能有的输出

/* $begin forkprob2 */
#include "csapp.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);
}
/* $end forkprob2 */

进程图:

在这里插入图片描述
可能输出就是拓扑排序,包括但不止:
112002、102120、100212
运行结果:
在这里插入图片描述
**8.19下面的函数会打印多少行输出?用一个n的函数给出答案。假设n>=1.

#include "csapp.h"

/* $begin forkprob8 */
void foo(int n) 
{
    
    
    int i;

    for (i = 0; i < n; i++) 
	Fork();
    printf("hello\n");
    exit(0);
}
/* $end forkprob8 */

int main(int argc, char **argv)
{
    
    
    if  (argc < 2) {
    
    
	printf("usage: %s <n>\n", argv[0]);
	exit(0);
    }
    foo(atoi(argv[1]));
    exit(0);
}

answer:2^n
解析:每个循环fork一次,一次fork有两个进程,每个进程打印一行hello,然后再进行fork。所以是一个指数的增长。

**8.21下面的程序的可能输出序列是什么?

#include "csapp.h"

/* $begin waitprob3 */
int main() 
{
    
    
    if (fork() == 0) {
    
    
	printf("a");
	exit(0);
    }  
    else {
    
    
	printf("b");
	waitpid(-1, NULL, 0);
    }
    printf("c");
    exit(0);
}
/* $end waitprob3 */

机器输出:
在这里插入图片描述
进程图:
在这里插入图片描述
一定是abc,除非中间加fflusn或者\n
因为当没有换行符的时候printf的输出其实并没有马上输出,而是放到了缓冲区中去。知道exit的时候进行输出,那么即使父进程先进行printf b的操作,b仍然在缓冲区中等待子进程结束打印完a后再继续运行知道父进程结束将bc输出。

到这里csapp书后关于fork和wait部分的题目写了一大半点点。顺着刚才的缓冲区和fork间的冲突我们来看看一些企业题目。

  • 课外习题
    1.以下程序将输出多少个*?
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void){
    
    
	int i;
	for(i=0;i<2;i++){
    
    

		fork();
        printf("*");
	}
	return 0;
}

answe:8
程序结果:
在这里插入图片描述
解释:
在这里插入图片描述
罪魁祸首就在于标准I/O并未在fork时就输出,而存在于缓冲区中。在fork时,子进程自动继承了父进程的缓冲区,导致比预期多输出两个*
再来看看另外一道好玩的
2.假设下面这个程序编译后名字是main,请问这个程序执行后,系统总共会出现多少个main?

#include <stdio.h>
#include <stdlib.h>
#include <csapp.h>
int main(int argc,char* argv[])
{
    
    
    Fork();
    Fork()&&Fork()||Fork();
    Fork();
    printf("*\n");
    sleep(5);
}

answer:产生了19个新的进程,一共有20个进程
在这里插入图片描述
在这里插入图片描述
进程图:在这里插入图片描述
具体解析:
关键在于第二局逻辑关系符的语句
先抽象为A&&B||C
就有如下关系式子:
在这里插入图片描述
对应到fork两次返回的值0和大于0,即子进程为假,父进程为真
那么就有fork2为假时,fork2&&fork3一定为假,就不用判断fork3即不执行,直接做fork4.
fork2为真实,fork2&&fork3的真值不确定,一定会执行fork3.如果fork3为假则还要执行fork4来进行判别,如果为真,则跳过fork4
所以这里有第一行fork生成了一个新的进程,现在一共有两个进程。
第二行的3个fork生成了5个进程。所以有2 * 5=10个进程。
第三行的fork生成了两个进程。所以有10 *2=20个进程。
相应的总共就有20个进程,减去原有的一个进程,就有19个新进程。

  • 一些额外的课堂代码
void fork0()
{
    
    
    if (fork() == 0) {
    
    
	printf("Hello from child\n");
    }
    else {
    
    
	printf("Hello from parent\n");
    }
}

机器运行结果:
在这里插入图片描述
在这里插入图片描述
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.

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

运行结果:
在这里插入图片描述
进程图:
在这里插入图片描述
3.

void fork3()
{
    
    
    printf("L0\n");
    pid_t pid;
    pid=fork();
    if(pid==0)printf("l1\n");
    else printf("L1\n");
    pid =fork();
    if(pid==0) printf("l2\n");
        else printf("L2\n");
    pid=fork();
    if(pid==0) printf("bye\n");
    else printf("Bye\n");
}

真机运行结果:
在这里插入图片描述
进程图:
在这里插入图片描述
4.

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

机器运行结果:
在这里插入图片描述
进程图:
在这里插入图片描述
5.

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

机器运行结果:
在这里插入图片描述
进程图:
在这里插入图片描述
6.

void cleanup(void) {
    
    
    printf("Cleaning up\n");
}
void fork6()
{
    
     
    atexit(cleanup);
    fork();
    exit(0);
}

机器执行结果:
在这里插入图片描述
进程图:
在这里插入图片描述
7.

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

机器运行结果:
在这里插入图片描述
父进程进入死循环,机器执行完子进程以后子进程结束进入僵死defunct状态。
用kill-9命令杀死父进程,则僵死的子进程被init进程回收
具体运行:
在这里插入图片描述
8.

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

机器运行结果:
在这里插入图片描述
父进程结束,子进程尚在运行,则子进程变成孤儿进程,进程终止时被init回收。在这里,子进程进入死循环,必须用kill指令强制杀死。
在这里插入图片描述
9.

void fork9()
{
    
    
    int child_status;

    if (fork() == 0) {
    
    
	printf("HC: hello from child\n");
	//sleep(10);
        exit(0);
    } else {
    
    
        //sleep(5);
	printf("HP: hello from parent\n");
	wait(&child_status);
	printf("CT: child has terminated\n");
    }
    printf("Bye\n");
}

程序运行结果:
在这里插入图片描述
进程图:
在这里插入图片描述
为了更好的帮助了解wait函数,可以用注释的两个sleep函数来看一下wait的效果
10.

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

程序运行结果:
在这里插入图片描述
进程图:
在这里插入图片描述
解释一下:首先fork的时候会把所有环境都复制一份,但是程序的第二个for循环其实可以看到发生在父进程中,父进程的pid数组保留了所有创建的子进程的ID号。然后for循环开始执行wait函数,随机找一个结束的子进程返回它的exit的值并回收该进程,然后打印出子进程。wpid时wait函数执行成功时返回的子进程的ID号,WEXITSTATUS(child_status)返回子进程exit的值。
11.

#define N 5
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);
    }
}

运行结果:
在这里插入图片描述
进程图:
在这里插入图片描述
解释一下:几乎和上面一个一样,只是这里调用的时waitpid函数并且pid给了指定的值。就是父进程记录的所有的子进程的ID号进行调用waitpid函数,等待ID号为pid[i]的进程结束,打印相关信息。

猜你喜欢

转载自blog.csdn.net/weixin_43329358/article/details/102932891