计算机系统fork函数学习日志

程序的加载和运行

问题:hello程序的加载和运行过程是怎样的?

step1:在shell命令行提示符后输入命令:$./hello,然后按下回车键
step2:在shell命令行解释器构造argv(参数列表)和envp(环境变量列表)
在这里插入图片描述
step3:调用fork()函数,创建一个子进程,和父进程shell完全相同(只读/共享),包括只读代码段、可读写数据段、堆以及用户栈等。
step4:调用execve()函数,在当前进程(新创建的子进程)的上下文中加载并运行hello程序。将hello中的.text节、.data节、.bss节等内容加载到当前进程的虚拟地址空间(仅修改当前进程上下文中关于存储映像的一些数据结构,不从磁盘拷贝代码和数据等内容)
step5:调用hello程序的main()函数,hello程序开始在一个进程的上下文中运行。

fork的基本知识

函数原型:pid_t fork(void);
返回值:若成功调用一次则返回两个值,子进程返回0,父进程返回子进程ID;否则,出错返回-1。

一个现有进程可以调用fork函数创建一个新进程。由fork函数创建的新进程被称为子进程。

fork函数例子

fork0.c

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[]) 
{
    if (fork() == 0) {
 	printf("Hello from child\n");
    }
    else {
 	printf("Hello from parent\n");
    }
    return 0;
}

输出结果:
在这里插入图片描述
进程图如下:
0

获取进程ID

每个进程都有一个唯一的正数(非零)进程ID(PID)。
getpid函数返回调用进程的PID。
getppid函数返回它的父进程的PID(创建调用进程的进程)。

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

pid_t getpid(void);
pid_t getppid(void);

fork1.c

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[]) 
{
    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);
    return 0;
}

运行结果:
1
进程图:
1
fork2.c

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[]) 
{   
    printf("L0\n");
    fork();
    printf("L1\n");    
    fork();
    printf("Bye\n");
    return 0;
}

运行结果:
2
进程图:
2
fork4.c

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[]) 
{
    printf("L0\n");
    if (fork() != 0) {
 	printf("L1\n");   
    	if (fork() != 0) {
        	printf("L2\n");
    	}
    }
    printf("Bye\n");
    return 0;
}

运行结果:
4
进程图:
4
fork5.c

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[]) 
{
    printf("L0\n");
    if (fork() == 0) {
 	printf("L1\n");    
 	if (fork() == 0) {
     		printf("L2\n");
 	}
    }
    printf("Bye\n");
    return 0;
}

运行结果:
5
进程图:
5

atexit()函数

函数声明:int atexit(void (*func)(void));
atexit()函数详解
fork6.c

#include <stdlib.h>// 使用atexit()函数必须包含的头文件stdlib.h
#include <stdio.h>
#include <unistd.h>
void cleanup(void) {
    printf("Cleaning up\n");
}
int main(int argc, char *argv[]) 
{
    atexit(cleanup);
    fork();
    exit(0);
}

运行结果:
6
进程图:
6

回收子进程

当一个进程由于某种原因终止时,内核并不是立即把它从系统中清除。相反,进程被保持在一种已终止的状态,直到被它的父进程回收
一个终止了但还未被回收的进程称为僵死进程
fork7.c

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[]) 
{
    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 */
    }
}

运行结果:
7
因为在父进程中有while(1); ,所以父进程没法结束,一直在死循环,回不到shell命令行

我们用CTRL+Z将其挂起在后台运行
CTRL+Z输入ps查看进程状态
ps
我们发现子进程是defunct状态(僵尸状态),我们尝试用kill来杀死它
输入如下命令

$kill -9 4117

再用ps查看进程状态
kill子进程
发现没被杀死
所谓斩草要除根,擒贼先擒王
我们来杀死它的爸爸(僵尸进程的父进程)
kill父进程
发现僵尸成功扫除干净 ~ ( ̄▽ ̄)~*

我们再来看一个子进程死循环的情况

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[]) 
{
    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);
    }
}

运行结果:
8
我们发现子进程一直在运行,没有终止
爸爸不等儿子直接把儿子抛弃了〒▽〒
但是没关系,我们可以叫父亲等等他的儿子( •̀ ω •́ )✧

一个进程可以调用waitpid函数来等待它的子进程终止或者停止。
wait函数和waitpid函数解析

pid_t wait(int *statusp);是waitpid函数的简化版本,调用wait(&status)等价于调用waitpid(-1,&statusp,0)

#include <sys/types.h>
#include <sys/wait.h>

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

fork9.c

int main()
{    
	int child_status;     
	if (fork() == 0) {  
		printf("HC: hello from child\n");        
		exit(0);    
	} else {  
		printf("HP: hello from parent\n");  
		wait(&child_status);  //调用wait函数,等到子进程终止了就执行下一条语句  
		printf("CT: child has terminated\n");  //这条语句一定是在HC的后面出现    
	}    
	printf("Bye\n");
}

运行结果:
9
fork10.c

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#define N 5
int main(int argc, char *argv[]) 
{
	int i, child_status;
    	pid_t pid;
    	for (i = 0; i < N; i++)
 		if ((pid = fork()) == 0) {
  			printf("%d已结束\n",i);
     			exit(100+i); /* Child */
 		}
    	while ((pid = wait(&child_status))>0) { /* Parent */
 		if (WIFEXITED(child_status))
     			printf("Child %d terminated with exit status %d\n",
     				pid, WEXITSTATUS(child_status));
 		else
     			printf("Child %d terminate abnormally\n", pid);
    		}

}

运行结果:
10
它使用waitpid,不按照特定的顺序等待它的所有N个子进程终止。父进程创建 N个子进程,每个子进程以一个唯一的退出状态退出。

父进程用 waitpid作为while循环的测试条件,等待它所有的子进程终
止。因为第一个参数是-1,所以对waitpid的调用会阻塞,直到任意一个子进程终止。

在每个子进程终止时,对waitpid的调用会返回,返回值为该子进程的非零的PID。if (WIFEXITED(child_status)) 检查子进程的退出状态。如果子进程是正常终止的——在此是以调用 exit 函数终止的——那么父进程就提取出退出状态,把它输出到stdout上。

我们做一个简单的改变,下面这个.c文件消除了这种不确定性,按照父进程创建子进程的相同顺序来回收这些子进程。

fork11.c

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#define N 5
int main(int argc, char *argv[]) 
{
	int i, child_status;
    	pid_t wpid,pid[N];
    	for (i = 0; i < N; i++)
 		if ((pid[i] = fork()) == 0) {
  			printf("%d已结束\n",i);
     			exit(100+i); /* Child */
 		}
 	i = 0;
    	while ((wpid = waitpid(pid[i++],&child_status,0))>0) { /* Parent */
 		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);
    	}
}

运行结果:
11


持续更新…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值