【Linux系统编程(进程编程)】创建进程的场景,fork和vfork的使用及区别

2024-03-23 22:28

本文主要是介绍【Linux系统编程(进程编程)】创建进程的场景,fork和vfork的使用及区别,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 一、进程关键概念
  • 二、创建进程函数fork的使用
    • 一、进程创建实战
  • 三、创建进程函数fork的使用补充
  • 四、进程创建发生了什么事?
  • 五、创建新进程的实际应用场景 & fork总结
    • 一、fork创建一个子进程的一般目的?
    • 二、fork编程实战
  • 六、vfork也能创建进程
    • 一、验证子进程先运行
    • fork
    • vfork
        • 子进程没有退出
        • 子进程有退出
    • 二、验证vfork子进程共享父进程的内存空间

一、进程关键概念

问1. 什么是程序,什么是进程,有什么区别?
问2. 如何查看系统中有哪些进程?
问3. 什么是进程标识符?
问4. 什么叫父进程,什么叫子进程?
问5. C程序的存储空间是如何分配?

问1. 什么是程序,什么是进程,有什么区别?
程序是静态的概念,进程是动态的概念。
程序是静态的概念,gcc xxx.c -o pro 磁盘中生成的pro文件,叫做程序。
进程是动态的概念,是程序的一次运行活动,通俗讲就是程序跑起来了,系统中多了一个进程。

问2. 如何查看系统中有哪些进程?
a. 使用ps指令查看
ps
ps -aux 生成一大堆
而实际使用ps配合grep查询程序是否存在某一个进程,如:ps -aux|grep init是查询init相关的进程。利用管道进行查询,避免显示太多,查找不方便。
b. 利用top指令查看,类似windows任务管理器,动态显示。

问3. 什么是进程标识符?
每个进程都有一个非负整数表示唯一ID,叫做pid,类似身份证。

pid=0:成为交换进程(swapper)
作用——进程调度
pid=1:init进程
作用——系统初始化

pos机显示刷卡界面,ktv点歌机显示点歌界面,这些由init进程来做。程序运行后,内核加载完毕,文件系统起来时候,运行第一个进程就是init进程,init进程就会读取配置文件,去启动一些其他的启动进程(其他的开机程序)。

编程调用getpid函数获取自身的进程标识符;getppid获取父进程的进程标识符。
image.png

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>int main()
{pid_t pid;pid = getpid();printf("this process pid is %d\n", pid);while(1);return 0;
}

问4. 什么叫父进程,什么叫子进程?
进程A创建了进程B
那么A叫做父进程,B叫做子进程,父子进程是相对的概念,理解为人类世界的父子关系。

二、创建进程函数fork的使用

一、进程创建实战

使用fork函数创建一个进程
pid_t fork(void);

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

image.png
image.png

请看printf输出,执行了两遍,这是为什么呢?
image.png

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>int main()
{pid_t pid;pid = getpid();fork();printf("this process pid is %d\n", pid);return 0;
}
~    

第十一行fork(),之前执行一次,之后因为创建了进程,所以执行了两次,看到了两次输出。

image.png

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>int main()
{pid_t pid;pid = getpid();fork();printf("this process pid is %d, current process id is %d\n", pid, getpid());return 0;
}

getpid()获取当前进程的进程id,可以用来区分进程。第十三行,如果两个id相同,说明都是pid的值,属于父进程,若是俩id不同,则是子进程,不同的id是子进程调用getpid()的原因。

进一步 多写一些调试信息:
image.png

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>int main()
{pid_t pid;pid = getpid();//原来进程IDfork();//创建进程//原来进程 与当前进程比较if(pid == getpid()){       //当前进程是原来进程,则为父进程printf("this is father process\n");}else{//当前进程不是原来进程,则为子进程printf("this is chlid process, pid:%d\n",getpid());}return 0;
}

fork()之前,包括fork()这行,都是父进程在运行。而if-else父子进程都会执行。
子进程和父进程都会执行fork后面if-else分支。而且父子进程会执行不同分支,一个执行if,另一个肯定执行else。因为他们都会执行这句话if里边的pid == getpid(),而父进程时,表达式和为真,子进程时,表达式为假。

通过pid区分父子进程
image.png

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>int main()
{pid_t pid;pid_t pid2;pid = getpid();//原来进程IDprintf("before fork pid = %d\n",pid);fork();//创建进程pid2 = getpid();printf("after fork pid = %d\n",pid2);//原来进程 与当前进程比较if(pid == pid2){	//当前进程是原来进程,则为父进程printf("this is father process\n");}else{//当前进程不是原来进程,则为子进程printf("this is chlid process, pid:%d\n",getpid());}return 0;
}

通过fork()函数返回值区分父子进程。返回值为0为子进程,返回非零值为父进程。
image.png

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>int main()
{pid_t pid;printf("father pid = %d\n",getpid());pid = fork();//创建进程if(pid > 0){printf("this is father process, pid:%d\n",getpid());}else if(pid == 0){printf("this is chlid process, pid:%d\n",getpid());}return 0;
}

image.png

三、创建进程函数fork的使用补充

fork返回值有可能是0,有可能是其他的。在父子进程都把retpid打印出来。
fork之后,新的进程拷贝了一份代码和变量,新进程和旧的进程都有一份retpid。fork之后,给retpid分配了一个0,一个非零。代码看一下:
image.png
进入父进程,打印的retpid是子进程的pid
进入子进程,打印的retpid是0
所以,fork后有了两个retpid变量,一个分配给父进程,一个分配给子进程。不同的是:当fork返回非零时,返回给父进程的retpid为子进程的进程id,而fork返回零时,返回给子进程的retpid为0。

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>int main()
{pid_t pid;pid_t pid2;pid_t retpid;pid = getpid();//pid是父进程printf("before fork pid = %d\n",pid);retpid = fork();//fork创建进程 返回retpidpid2 = getpid();printf("after fork pid = %d\n",pid2);if(pid == pid2){printf("this is father process. retpid:%d\n", retpid);}else{printf("this is chlid process. retpid:%d, child pid:%d\n", retpid, getpid());}return 0;
}

也就是说,函数fork调用成功,则:(man手册中翻译理解)

  • 在父进程中,返回子进程PID
  • 在子进程中,返回0

四、进程创建发生了什么事?

image.png
局部变量a的分配,不确定。
image.png
代码段共享
数据段拷贝(写时拷贝)
以前是全部拷贝,现在是写时拷贝。
image.png


#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>int main()
{pid_t pid;int data = 10;printf("father pid = %d\n",getpid());pid = fork();//创建进程if(pid > 0){printf("this is father process, pid:%d\n",getpid());}else if(pid == 0){printf("this is chlid process, pid:%d\n",getpid());}printf("data=%d\n",data);return 0;
}

如下,子进程对data修改,会执行写时拷贝。
image.png

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>int main()
{pid_t pid;int data = 10;printf("father pid = %d\n",getpid());pid = fork();//创建进程if(pid > 0){printf("this is father process, pid:%d\n",getpid());}else if(pid == 0){data += 10;printf("this is chlid process, pid:%d\n",getpid());}printf("data=%d\n",data);return 0;
}

五、创建新进程的实际应用场景 & fork总结

一、fork创建一个子进程的一般目的?

(1)一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的——父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求到达。

下边模拟一下网络请求,为每个请求创建一个服务进程。

(现在还存在select poll epoll等IO多路复用技术,暂不展开)

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>int main()
{pid_t pid;int data = 10;while(1){printf("please input a data:");scanf("%d", &data);if(data == 1){pid = fork();if(pid > 0){}else if(pid == 0){while(1){printf("do net request, response to :%d\n", data);sleep(5);}}}else{printf("wait, do nothing\n");}}return 0;
}

(2)一个进程要执行一个不同的程序。这对shell是常见的情况。在这种情况下,子进程返回后立即调用exec

二、fork编程实战

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

六、vfork也能创建进程

vfork函数 也可以创建进程,与fork有什么区别??
关键区别一:
vfork 直接使用父进程存储空间,不拷贝。
关键区别二:
vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行。

一、验证子进程先运行

fork

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>int main()
{pid_t pid;pid = fork();if(pid > 0){while(1){printf("this is father process, pid:%d\n",getpid());sleep(1);}}else if(pid == 0){while(1){printf("this is chlid process, pid:%d\n",getpid());sleep(1);}}return 0;
}

image.png
结果证明:使用fork函数创建进程,父子进程同时运行。

vfork

子进程没有退出

在刚刚代码基础上 仅仅把fork换成vfork

执行效果:
image.png
结果说明:子进程没退出,父进程就不执行。

子进程有退出

子进程执行三次,子进程退出。

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>int main()
{pid_t pid;int cnt = 0;pid = vfork();if(pid > 0){while(1){printf("this is father process, pid:%d\n",getpid());sleep(1);}}else if(pid == 0){while(1){printf("this is chlid process, pid:%d\n",getpid());sleep(1);cnt++;if(cnt == 3){break;}}}return 0;
}        

image.png
结果说明:子进程退出后,父进程才执行。

二、验证vfork子进程共享父进程的内存空间

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main()
{pid_t pid;int cnt = 0;pid = vfork();if(pid > 0){while(1){printf("cnt=%d\n", cnt);printf("this is father process, pid:%d\n",getpid());sleep(1);}}else if(pid == 0){while(1){printf("this is chlid process, pid:%d\n",getpid());sleep(1);cnt++;if(cnt == 3){exit(0);//break;}}}return 0;
}

image.png
结果说明:只有子进程在修改cnt。使用vfork子进程调用结束后,父进程中cnt的值发生改变,说明被子进程修改。所以vfork父子共享内存空间。

这篇关于【Linux系统编程(进程编程)】创建进程的场景,fork和vfork的使用及区别的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/839690

相关文章

使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)

《使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)》在现代软件开发中,处理JSON数据是一项非常常见的任务,无论是从API接口获取数据,还是将数据存储为JSON格式,解析... 目录1. 背景介绍1.1 jsON简介1.2 实际案例2. 准备工作2.1 环境搭建2.1.1 添加

如何使用celery进行异步处理和定时任务(django)

《如何使用celery进行异步处理和定时任务(django)》文章介绍了Celery的基本概念、安装方法、如何使用Celery进行异步任务处理以及如何设置定时任务,通过Celery,可以在Web应用中... 目录一、celery的作用二、安装celery三、使用celery 异步执行任务四、使用celery

使用Python绘制蛇年春节祝福艺术图

《使用Python绘制蛇年春节祝福艺术图》:本文主要介绍如何使用Python的Matplotlib库绘制一幅富有创意的“蛇年有福”艺术图,这幅图结合了数字,蛇形,花朵等装饰,需要的可以参考下... 目录1. 绘图的基本概念2. 准备工作3. 实现代码解析3.1 设置绘图画布3.2 绘制数字“2025”3.3

JAVA中整型数组、字符串数组、整型数和字符串 的创建与转换的方法

《JAVA中整型数组、字符串数组、整型数和字符串的创建与转换的方法》本文介绍了Java中字符串、字符数组和整型数组的创建方法,以及它们之间的转换方法,还详细讲解了字符串中的一些常用方法,如index... 目录一、字符串、字符数组和整型数组的创建1、字符串的创建方法1.1 通过引用字符数组来创建字符串1.2

Jsoncpp的安装与使用方式

《Jsoncpp的安装与使用方式》JsonCpp是一个用于解析和生成JSON数据的C++库,它支持解析JSON文件或字符串到C++对象,以及将C++对象序列化回JSON格式,安装JsonCpp可以通过... 目录安装jsoncppJsoncpp的使用Value类构造函数检测保存的数据类型提取数据对json数

python使用watchdog实现文件资源监控

《python使用watchdog实现文件资源监控》watchdog支持跨平台文件资源监控,可以检测指定文件夹下文件及文件夹变动,下面我们来看看Python如何使用watchdog实现文件资源监控吧... python文件监控库watchdogs简介随着Python在各种应用领域中的广泛使用,其生态环境也

Python中构建终端应用界面利器Blessed模块的使用

《Python中构建终端应用界面利器Blessed模块的使用》Blessed库作为一个轻量级且功能强大的解决方案,开始在开发者中赢得口碑,今天,我们就一起来探索一下它是如何让终端UI开发变得轻松而高... 目录一、安装与配置:简单、快速、无障碍二、基本功能:从彩色文本到动态交互1. 显示基本内容2. 创建链

Linux磁盘分区、格式化和挂载方式

《Linux磁盘分区、格式化和挂载方式》本文详细介绍了Linux系统中磁盘分区、格式化和挂载的基本操作步骤和命令,包括MBR和GPT分区表的区别、fdisk和gdisk命令的使用、常见的文件系统格式以... 目录一、磁盘分区表分类二、fdisk命令创建分区1、交互式的命令2、分区主分区3、创建扩展分区,然后

Linux中chmod权限设置方式

《Linux中chmod权限设置方式》本文介绍了Linux系统中文件和目录权限的设置方法,包括chmod、chown和chgrp命令的使用,以及权限模式和符号模式的详细说明,通过这些命令,用户可以灵活... 目录设置基本权限命令:chmod1、权限介绍2、chmod命令常见用法和示例3、文件权限详解4、ch

springboot整合 xxl-job及使用步骤

《springboot整合xxl-job及使用步骤》XXL-JOB是一个分布式任务调度平台,用于解决分布式系统中的任务调度和管理问题,文章详细介绍了XXL-JOB的架构,包括调度中心、执行器和Web... 目录一、xxl-job是什么二、使用步骤1. 下载并运行管理端代码2. 访问管理页面,确认是否启动成功