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

news2024/11/18 20:27:14

文章目录

  • 一、进程关键概念
  • 二、创建进程函数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();//原来进程ID

        fork();//创建进程


        //原来进程 与当前进程比较
        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();//原来进程ID

	printf("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创建进程 返回retpid


        pid2 = 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父子共享内存空间。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1540340.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Day55-LNMP架构原理及实战精讲

Day55-LNMP架构原理及实战精讲 1.什么是LNMP?2.LNMP架构工作原理流程解析3.LNMP架构环境安装部署4. LNMP架构配置及整体测试实战5. 超越企业级标准搭建和优化blog软件5.1 部署博客blog软件5.2 部署知乎软件Wecenter 1.什么是LNMP? 2010年前 LAMP Linux、Apache、MySQL、PHP&…

能降低嵌入式系统功耗的三个技术

为电池寿命设计嵌入式系统已经成为许多团队重要的设计考虑因素。优化电池寿命的能力有助于降低现场维护成本&#xff0c;并确保客户不需要不断更换或充电电池&#xff0c;从而获得良好的产品体验。 团队通常使用一些标准技术来提高电池寿命&#xff0c;例如将处理器置于低功耗…

企业微信可以更换公司主体吗?

企业微信变更主体有什么作用&#xff1f;当我们的企业因为各种原因需要注销或已经注销&#xff0c;或者运营变更等情况&#xff0c;企业微信无法继续使用原主体继续使用时&#xff0c;可以申请企业主体变更&#xff0c;变更为新的主体。企业微信变更主体的条件有哪些&#xff1…

上位机图像处理和嵌入式模块部署(qmacvisual图像识别)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 所谓图像识别&#xff0c;就是对图像进行分类处理&#xff0c;比如说判断图像上面的物体是飞机、还是蝴蝶。在深度学习和卷积神经网络CNN不像现在这…

2024-03-23 作业

网络聊天室 运行代码&#xff1a; main.c #include "link.h" #define SER_PORT 8888 int main(int argc, const char *argv[]) {if(argv[1]NULL){printf("请输入IP地址\n");return 0;}//创建套接字文件int sfd socket(AF_INET,SOCK_DGRAM,0);if(sfd …

【计算机网络】常见面试题汇总

文章目录 1.计算机网络基础1.1网络分层模型/OSI七层模型是什么&#xff1f;1.2TCP/IP四层模型是什么&#xff1f;每一层的作用&#xff1f;1.2.1TCP四层模型&#xff1f;1.2.2为什么网络要分层&#xff1f; 1.2常见网络协议1.2.1应用层常见的协议1.2.2网络层常见的协议 2.HTTP2…

MongoDB知识

1、部署MongoDB &#xff08;1&#xff09;new好一个mongo文件之后执行 &#xff08;出现mongodb.key&#xff09;记得放行端口 openssl rand -base64 666 > mongodb.key &#xff08;2&#xff09;放到一个docker-compose.yml之后docker-compose up -d执行 version: 3.…

数学(算法竞赛、蓝桥杯)--快速幂

1、B站视频链接&#xff1a;G01 快速幂_哔哩哔哩_bilibili 题目链接&#xff1a;P1226 【模板】快速幂 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) #include <bits/stdc.h> using namespace std; typedef long long LL; int a,b,p; int quickpow(LL a,int n,int p){…

【wails】(10):研究go-llama.cpp项目,但是发现不支持最新的qwen大模型,可以运行llama-2-7b-chat

1&#xff0c;视频演示地址 2&#xff0c;项目地址go-llama.cpp 下载并进行编译&#xff1a; git clone --recurse-submodules https://github.com/go-skynet/go-llama.cpp cd go-llama.cpp make libbinding.a项目中还打了个补丁&#xff1a; 给 编译成功&#xff0c;虽然有…

力扣面试150 阶乘后的零 数论 找规律 质因数

Problem: 172. 阶乘后的零 思路 &#x1f468;‍&#x1f3eb; 大佬神解 一个数末尾有多少个 0 &#xff0c;取决于这个数 有多少个因子 10而 10 可以分解出质因子 2 和 5而在阶乘种&#xff0c;2 的倍数会比 5 的倍数多&#xff0c;换而言之&#xff0c;每一个 5 都会找到一…

vue3与Electron构建跨平台应用(webpack)

一、创建vue3项目 vue create vue3_webpack_electron 二、安装Electron npm install --save-dev electron Electron 三、vue add electron-builder vue add electron-builder

VMware中UbuntuServer扩展硬盘空间

VMware中UbuntuServer扩展硬盘空间 没有不可治愈的伤痛&#xff0c;没有不能结束的沉沦&#xff0c;所有失去的&#xff0c;会以另一种方式归来 ——【约翰-肖尔斯】 第一步 lxalxa:~$ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS loop0 …

鸿蒙Harmony应用开发—ArkTS-像素单位

ArkUI为开发者提供4种像素单位&#xff0c;框架采用vp为基准数据单位。 说明&#xff1a; 本模块首批接口从API version 7开始支持&#xff0c;后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 名称描述px屏幕物理像素单位。vp屏幕密度相关像素&#xff0c;…

OpenCV学习笔记(十一)——利用Sobel算子计算梯度

Sobel算子是基于一阶导数的离散差分算子&#xff0c;其中Sobel对于像素值的变化是十分敏感的&#xff0c;在进行边缘检测的时候&#xff0c;Sobel算子常用于对周围像素的重要性进行检测。 Sobel算子包括检验水平方向的算子和检测竖直方向的算子 计算机梯度值的操作如下&#x…

XUbuntu22.04之安装Plantuml(二百二十三)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

微信小程序Skyline搜索框吸顶到navtab胶囊位置,丝滑Q弹动画

进入下面小程序可以体验效果&#xff1a; 基于官方原版demo加入了回弹动画 WXML: <scroll-viewclass"scroll-area"type"custom"scroll-yshow-scrollbar"{{false}}"worklet:onscrollend"handleScrollEnd"worklet:onscrollupdate&q…

【工具】cassetteai — 制作音乐就像现在写提示一样简单

Cassette 是一种人工智能驱动的音乐创作工具,使各种技能水平的用户都可以根据自己的特定需求和偏好生成高质量、免版税的音乐曲目。它基于基于潜在扩散 (LDM) 的机器学习模型,可以使用用户提供的文本描述来想象节拍。它具有易于使用的界面,用户可以输入各种参数,例如所需的…

Qt播放音乐代码示例

主界面 点击play按钮播放或暂停音乐&#xff0c;拖动进度条&#xff0c;音乐对应播放。 QWidget window;QPushButton* playButton new QPushButton("Play");// Qt 播放音乐// 创建 QMediaPlayer 对象QMediaPlayer* player new QMediaPlayer;// 指定音频文件的路径…

查立得php+mysql源码通用数据库配置教程

适用范围&#xff1a; 查分吧PHP多条件都输对版已有表万用查询系统 phpMySql已有数据表通用搜索可增删改查 查立得快搜系统(phpMysql) v20220208 查立得万能查&#xff08;phpmysql&#xff09; v20220512 及 各付费版 等几十款源码 数据库配置路径 数…

Redis的String类型为什么重新设计使用了SDS数据结构呢

Redis 选择重新设计其 String 类型的底层数据结构&#xff0c;采用 SDS&#xff08;Simple Dynamic String&#xff09;而不是直接使用 C 语言标准库提供的原生字符串&#xff08;char*&#xff09;的原因主要包括以下几点&#xff1a; O(1) 时间复杂度获取长度&#xff1a; 在…