linux进程——父子进程(十三)

目录

一 什么是进程

1.1 进程概念

 1.2 获取进程PID号 —— getpid()

1.3 getppid()获取父进程PID号

二 fork()创建子进程

2.1 fork()函数原型:

2.2 使用示例 1使用 fork()创建子进程。

三 fork创建新进程时发生了什么事?

3.1 fork()函数之后,子进程会获得父进程所有文件描述符的副本

 3.2父、子进程中对应的文件描述符指向了相同的文件表 ——代码测试

四 linux 全拷贝和写实拷贝

4.1全拷贝:

4.2写实拷贝:

五 vfork()函数

5.1 vfork函数和fork函数的区别

5.2 vfork代码测试:

 5.3fork()代码测试


一 什么是进程

1.1 进程概念

进程其实就是一个可执行程序的实例,可执行程序就是一个可执行文件,文件是一个 静态的概念,存放磁盘中,如果可执行文件没有被运行,那它将不会产生什么作用,当它被运行之后,它将 会对系统环境产生一定的影响,所以可执行程序的实例就是可执行文件被运行。

进程是一个动态过程,而非静态文件,它是程序的一次运行过程,当应用程序被加载到内存中运行之后 它就称为了一个进程,当程序运行结束后也就意味着进程终止,这就是进程的一个生命周期。

 1.2 获取进程PID号 —— getpid()

Linux 系统下的每一个进程都有一个进程号(process ID,简称 PID),进程号是一个正数,用于唯一标 识系统中的某一个进程。

在应用程序中,可通过系统调用 getpid()来获取本进程的进程号,其函数原型如下所示:

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

应用代码测试:

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

int main(void) 

{ 
 pid_t pid = getpid(); 
 printf("本进程的 PID 为: %d\n", pid); 
 
 exit(0); 

} 

1.3 getppid()获取父进程PID号

除了 getpid()用于获取本进程的进程号之外,还可以使用 getppid()系统调用获取父进程的进程号,其函 数原型如下所示:

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

返回值:返回父进程的PID号 

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

int main(void) 

{ 
 pid_t pid = getpid(); //获取本进程 pid 

 printf("本进程的 PID 为: %d\n", pid); 
 
 pid = getppid(); //获取父进程 pid 

 printf("父进程的 PID 为: %d\n", pid); 
 
 exit(0); 

} 

二 fork()创建子进程

一个现有的进程可以调用 fork()函数创建一个新的进程,调用 fork()函数的进程称为父进程,由 fork()函 数创建出来的进程被称为子进程(child process),fork()函数原型如下所示(fork()为系统调用):

2.1 fork()函数原型:

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

返回值:

  • 失败返回-1 不创建子进程,并设置 errno 
  • 返回值为非负数为父进程
  • 返回值为0时是子进程 

在诸多的应用中,创建多个进程是任务分解时行之有效的方法,譬如,某一网络服务器进程可在监听客 户端请求的同时,为处理每一个请求事件而创建一个新的子进程,与此同时,服务器进程会继续监听更多的 客户端连接请求。在一个大型的应用程序任务中,创建子进程通常会简化应用程序的设计,同时提高了系统 的并发性(即同时能够处理更多的任务或请求,多个进程在宏观上实现同时运行)。

理解 fork()系统调用的关键在于,完成对其调用后将存在两个进程,一个是原进程(父进程)、另一个 则是创建出来的子进程,并且每个进程都会从 fork()函数的返回处继续执行,会导致调用 fork()返回两次值, 子进程返回一个值、父进程返回一个值。在程序代码中,可通过返回值来区分是子进程还是父进程。 fork()调用成功后,将会在父进程中返回子进程的 PID,而在子进程中返回值是 0;如果调用失败,父进 程返回值-1,不创建子进程,并设置 errno。 fork()调用成功后,子进程和父进程会继续执行 fork()调用之后的指令,子进程、父进程各自在自己的进 程空间中运行。事实上,子进程是父进程的一个副本,譬如子进程拷贝了父进程的数据段、堆、栈以及继承 了父进程打开的文件描述符,父进程与子进程并不共享这些存储空间,这是子进程对父进程相应部分存储 空间的完全复制,执行 fork()之后,每个进程均可修改各自的栈数据以及堆段中的变量,而并不影响另一个进程。

2.2 使用示例 1使用 fork()创建子进程。

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


int main()
{
	int pid =  fork(); //父进程的返回值pid为子进程的pid号,子进程的返回值为0
	switch (pid)
	{
		case -1:
			printf("fork error\n");
			break;
		case 0:
			printf("this is child ;PID = %d\n",getpid());
			break;
		default:
			printf("this is father;PID = %d\n",getpid());
			break;
	}

	return 0;
}

 从打印结果可知,fork()之后的语句被执行了两次,所以 switch…case 语句被执行了两次

  • 第一次进入 到了"case 0"分支,表示进入子进程
  • 第二次进入到了 default 分支,表示当前处于父进程

父进程的返回值pid为子进程的id号,子进程的返回值为0 

三 fork创建新进程时发生了什么事?

3.1 fork()函数之后,子进程会获得父进程所有文件描述符的副本

调用 fork()函数之后,子进程会获得父进程所有文件描述符的副本,这些副本的创建方式类似于 dup(), 这也意味着父、子进程对应的文件描述符均指向相同的文件表,如下图所示:

由此可知,子进程拷贝了父进程的文件描述符表,使得父、子进程中对应的文件描述符指向了相同的文件表,也意味着父、子进程中对应的文件描述符指向了磁盘中相同的文件,因而这些文件在父、子进程间实 现了共享,譬如,如果子进程更新了文件偏移量,那么这个改变也会影响到父进程中相应文件描述符的位置 偏移量。  

 3.2父、子进程中对应的文件描述符指向了相同的文件表 ——代码测试

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#define MY_FILE "./file.txt"

int main()
{
	char buf [128];
	int fd = open(MY_FILE,O_RDWR);
	if(-1 == fd)
	{
		perror("open error? :");
		exit(-1);
	}

	int pid =  fork();
	switch (pid)
	{
		case -1:
			printf("fork error\n");
			break;
		case 0:
			for(int i = 0;i<4;i++)
			{
				write(fd,"    child",strlen("    child"));
			}
			printf("子进程\n");
			close(fd);

			break;
		default:
			printf("父进程\n");
			for(int i = 0;i<4;i++)
			{
				write(fd,"    father",strlen("    father"));
			}

			close(fd);
			break;
	}

	read(fd,buf,128);
	printf("read :%s\n",buf);
	return 0;
}

上述代码中,父进程 open 打开文件之后,才调用 fork()创建了子进程,所以子进程了继承了父进程打 开的文件描述符 fd,我们需要验证的便是两个进程对文件的写入操作是分别各自写入、还是每次都在文件 末尾接续写入

 

有上述测试结果可知,此种情况下,父、子进程分别对同一个文件进行写入操作,结果是接续写,不管 是父进程,还是子进程,在每次写入时都是从文件的末尾写入,很像使用了 O_APPEND 标志的效果。其原 因也非常简单,子进程继承了父进程的文件描述符,两个文件描述符都指向了一个相同的文件表,意味着它们的文件偏移量是同一个、绑定在了一起,相互影响,子进程改变了文件的位置 偏移量就会作用到父进程,同理,父进程改变了文件的位置偏移量就会作用到子进程

但是执行结果不一定是父进程先执行或者子进程先执行,这个不一定的,这个要看系统的调度了 

四 linux 全拷贝和写实拷贝

4.1全拷贝:

传统的fork ()系统调用直接把所有的资源复制给新创建的进程。 这种实现过于简单并且效率低下,因为它拷贝的数据也许并不共享,更糟的情况是,如果新进程打算立即执行一个新的映像,那么所有的拷贝都将前功尽弃。 

4.2写实拷贝:

Linux的fork ()使用写时拷贝(copy-on-write)页实现。 写时拷贝是一种可以推迟甚至免除拷贝数据的技术。 内核此时并不复制整个进程地址空间,而是让父进程和子进程共享同一个拷贝。 只有在需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝。 

五 vfork()函数

5.1 vfork函数和fork函数的区别

  • 关键区别一:vfork直接使用父进程存储空间,不拷贝
  • 关键区别二:vfork保证子进程先运行,当子进程调用exit退出时,父进程才开始执行

5.2 vfork代码测试:

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


int main()
{
	int data =10; //测试数据
	int pid =  vfork();
	switch (pid)
	{
		case -1:
			printf("fork error\n");
			break;
		case 0:
			printf("this is child ;PID = %d\n",getpid());
			printf("pid = %d\n",pid);
			data =data + 100;
			exit(0);

			break;
		default:
			printf("this is father;PID = %d\n",getpid());
			printf("pid = %d\n",pid);
			printf("data = %d \n",data);
			break;
	}

	return 0;
}

根据上述代码,man函数进来,创建一个int型的变量data,在子进程加100,在父进程中打印,最data为110,证明公用了一块地址

 5.3fork()代码测试

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


int main()
{
	int data = 10;
	int pid =  fork();
	switch (pid)
	{
		case -1:
			printf("fork error\n");
			break;
		case 0:
			printf("this is child ;PID = %d\n",getpid());
			printf("pid = %d\n",pid);
			data += 100;
			printf("子进程的data = %d\n",data);


			break;
		default:
			printf("this is father;PID = %d\n",getpid());
			printf("pid = %d\n",pid);
			printf("父进程的data = %d\n",data);


			break;
	}

	return 0;
}

 

而fork()代码中,data在子进程中进行自加100,在打印结果就是子进程打印的data就是100,而父进程的data为10,没有改变,因为在父子进程在写入新的数据的时候,数据会被复制,从而使各个进程拥有各自的拷贝

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@ChenPi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值