2021-06-27嵌入式学习----创建进程函数fork原理

使用fork函数创建一个进程

#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);

fork函数调用成功,返回两次
返回值为0, 代表当前进程是子进程;
返回值非负数,代表当前进程为父进程。
调用失败,返回-1

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

int main()
{
	pid_t pid;

	printf("father:pid= %d\n",getpid());
	//pid = getpid();   获取进程id
	pid = fork();        //创建进程,返回两次
	if(pid >0){
		printf("this is father print, pid = %d\n", getpid());
	}else if(pid == 0){
		printf("this is child print, pid = %d\n", getpid());
	}
	//printf("pid = %d\n", pid);打印两次
	return 0;
}
//运行结果
father:pid= 5590
this is father print, pid = 5590
this is child print, pid = 5591

进程调用fork
*系统会分配新的内存块和内核数据结构给子进程
*将父进程的部分数据结构写时拷贝给子进程
*系统添加子进程到系统进程列表中
*fork 返回,调用器开始调度

新的子进程会继承父进程的资源(堆栈,数据空间),操作系统会把父进程的虚拟地址空间拷贝一份给子进程作为地址空间,两个进程分别独立,虽然代码相同,但是却不共享,各自有一份数据.

过去父子进程拷贝是全部拷贝父进程的,现在是写时拷贝

写时拷贝:
就像名字一样,子进程只有在写的时候才进行拷贝,如果子进程并不修改资源,那么就会一直共享父进程的资源,每个子进程只要保存指向这个资源的指针就行了,这样做可以提高效率,减少复制带来的开销
如果子进程要修改资源,父进程拷贝一份它的地址空间给子进程,让子进程在它的地址空间里面随意修改,这样就不会影响到父进程了

当进程调用 fork 完成后 ,会有两个返回值,子进程返回0,父进程返回子进程的 pid,一般大于0,通过返回值判断进程是父进程还是子进程,另外也有两个函数getpid,getppid 分别获取子进程 pid 和父进程 pid,至于父进程和子进程的执行顺序是未知的,这是由调度器决定

fork创建一个子进程的一般目的
1)一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的—父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则等待下一个服务请求到达。
2)一个进程要执行一个不同的程序,这对shell是常见的情况,在这种情况下,子进程从fork返回后立即调用exec

fork编程实战
由fork创建的新进程被称为子进程。fork函数被调用一次,但返回两次。两次返回的唯一区别是子进程的返回值是0,而父进程的返回值则是新子进程的进程ID。将子进程id返回给父进程的理由是:因为一个进程的子进程可以有多个,并且没有一个函数使一个进程可以获得其所有的紫禁城的进程id。fork使子进程得到返回值0的理由是:一个进程只会有一个父进程,所以子进程总是可以调用getpid以获得其父进程的进程ID(进程ID 0总是由内核交换进程使用,所以一个子进程的进程ID不可能为0)
子进程和父进程继续执行fork调用之后的指令,子进程是父进程的副本。例如,子进程获得父进程的数据空间、堆和栈的副本。注意,这是子进程所拥有的副本。父、子进程不可共享这些存储空间部分。父、子进程共享正文段。
由于在fork之后经常跟随着exec,所以现在的很多实现并不进行一个父进程数据段,栈和堆的完全复制。作为替代,使用了写时复制(Copy-On-Write, COW)技术。这些区域由父、子进程共享,而且内核将它们的访问权限改变为只读的。如果父、子进程中的任一个试图修改这些区域,则内核只为修改区域的那块内存制作一个副本,通常是虚拟存储器系统中的一“页”。

vfork函数与fork区别,vfork也可以创建进程
1)vfork 直接使用父进程存储空间,不拷贝
2)vfork 保证子进程先运行,当子进程调用exit退出后,父进程才执行。

进程退出
正常退出
1)main函数调用return
2)进程调用exit(),标准c库
3)进程调用_exit()或者_Exit(),属于系统调用

补充:
1)进程最后一个线程返回
2)最后一个线程调用pthread_exit

异常退出
1)调用abort
2)当进程收到某些信号时,如ctrl+c
3)最后一个线程对取消(cancallation)请求做出相应

不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它使用的存储器等。
对上述任意一种终止情形,我们都希望终止进程能够通知其父进程它是如何终止的。对于三个终止函数(exit,_exit和_Exit),实现这一点的方法是,将其退出状态作为参数传送给函数。在异常终止情况下,内核(不是进程本身)产生一个指示其异常终止原因的终止状态。在任意一种情况下,该终止进程的父进程都用wait或waitpid函数取得其终止状态。

父进程等待子进程退出
父进程等待子进程退出,并收集子进程的退出状态
子进程退出状态不被收集,变成僵尸进程(Z+) ,可通过指令 ps -aux | grep查看进程状态

#include <sys/types.h>
#include <sys/wait.h>
 pid_t wait(int *wstatus);

wstatus参数:
是一个整型数指针
非空:子进程退出状态放在它所指向的地址中
空 : 不关心退出状态
检测,调用WEXTSTATUS(status)子进程返回的

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

int mian()
{
	pid_t pid;
	int cnt = 0;
	int status;

	pid = fork();
	if(pid > 0){
		wait(&status);//等待子进程退出
		printf("child quit status = %d\n", WEXITSTATUS(status));//获取子进程退出状态
		while(1){
			printf("this is father pid = %d\n", getpid());
			sleep(3);
		}
	}else if(pid == 0){
		while(1){
			printf("this is child pid = %d\n", getpid());
			cnt++;
			if(cnt == 3){
				exit(1);
			}
		}
	}
	return 0;
}

1)如果其所有子进程都还在运行,父进程则阻塞;
2)如果一个子进程已终止,正等待父进程获取其终止状态立即返回;
3)如果他没有任何子进程,则立即出错返回。

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

pid参数的作用
pid == -1 等待任一子进程,就这一方面,waitpid和wait等效
pid >0 等待其进程ID与pid相等的子进程
pid == 0 等待其组ID等于调用进程组ID的任一子进程
pid < -1 等待其组ID等于pid绝对值的任一子进程
option常量
WNOHANG 若由pid指定的子进程并不是立即可用的,则waitpid不阻塞,此时其返回值为0,父子进程同时运行

孤儿进程
父进程如果不等待子进程退出,在子进程之前就结束了,此时子进程叫做孤儿进程。
Linux避免系统存在过多的孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值