Linux:进程入门(进程与程序的区别,进程的标识符,fork函数创建多进程)

往期文章:《Linux:深入了解冯诺依曼结构与操作系统》

Linux:深入理解冯诺依曼结构与操作系统-CSDN博客

目录

1. 概念

2. 描述进程

3. 深入理解进程的本质

4. 进程PID

4.1 指令获取PID

4.2 geipid函数获取PID

4.3 kill指令终止进程

4.4 进程信息文件夹

(1)exe

(2)cwd

5. 进程PPID

5.1 getppid函数获取PPID

5.2 fork函数创建子进程

6. 创建多进程

7. fork函数如何返回两个值


1. 概念

进程(Process)是指一个正在执行的程序实例。在操作系统中,进程是资源分配和调度的基本单位。可执行程序与进程的区别:可执行程序是一组指令的集合,它存储在磁盘上,而进程则是程序在执行时的一个实例,它存在于内存中

2. 描述进程

操作系统也称之为Operating System,缩写就是OS。

操作系统笼统的分为内核(kernel)和外壳程序。内核有进程管理、内存管理、文件管理和驱动管理。操作系统管理任何对象,都是对其属性进行管理,遵守先描述,再组织的原则。

因此,操作系统管理进程,相当于管理它的属性,会把进程属性放在一个叫做进程信息控制块的数据结构当中,就是进程属性的集合。这也称为PCB(process control block),Linux操作系统下的PCB是task_struct

下面是task_struct结构体中包含的内容:

  • 标示符: 描述本进程的唯一标示符,用来区别其他进程。
  • 状态: 任务状态,退出代码,退出信号等。
  • 优先级: 相对于其他进程的优先级。
  • 程序计数器: 程序中即将被执行的下一条指令的地址。
  • 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
  • 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
  • I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
  • 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
  • 其他信息

3. 深入理解进程的本质

如下图,进程的前身就是一个可执行程序,它主要包含代码和数据,且在硬盘当中。可执行程序在windows系统中一般以.exe结尾,在Linux系统中一般没有特定的后缀结尾,且通常不跟后缀。

当一个可执行程序启动运行时,其代码与数据将被载入内存之中。在这一过程之前,操作系统内核会创建一个名为task_struct的结构体对象,该对象详细记录了进程的各种属性。这些属性包括但不限于进程标识符(pid)、状态信息、优先级等。特别地,memptr指针负责记录内存中myexe程序的起始地址,确保进程能够正确地访问和执行程序代码。

进程 = 内核数据结构(task_struct) + 程序的代码和数据

当多个可执行程序同时运行时,task_struct结构体内部采用特定的数据结构,将所有进程串联起来。设想task_struct中包含一个指向下一个结构体的指针,那么将存在一个结构体指针list,它指向链表中的第一个task_struct对象。随着新进程的创建,它们的task_struct对象会被前一个对象的next指针串联起来,从而在操作系统中形成了一个调度队列

CPU的寄存器并不直接存储程序的代码和数据,而是通过list指针定位到相应的task_struct对象,并将其信息载入寄存器。因此,当CPU调度内存中的myexe进程时,实际上是在操作该进程的task_struct对象,进而间接执行程序代码。

我们可以用一个类比来理解这个过程:假设张三正在求职,他提交了一份简历给公司的人力资源部门(HR)。这份简历包含了张三的个人详细信息、实习经历和项目经验,这些信息集合就如同进程的task_struct结构体,记录了张三的“属性”。当HR收到众多简历并逐一审核时,求职者本人对于这一过程是一无所知的。这与CPU调度进程的情形相似,CPU并不直接运行内存中的程序,而是通过操作task_struct结构体对象来间接实现对进程的调度。

4. 进程PID

4.1 指令获取PID

不管是在windows系统中,你双击某个应用,还是在Linux系统中,你执行某些指令或者运行某个可执行程序,当这些程序跑起来都叫做进程。不过进程一般分为两种,第一种是执行完就退出的进程,像Linux系统下的ls pwd等指令;第二种是执行完一直不退出,直到用户关闭,像window系统中的QQ,微信等应用,这种进程叫做常驻型进程。

下面写一份C语言代码,在main函数使用while循环搞个死循环,内部是每隔一秒钟打印一句话。sleep函数的作用是使当前进程暂停执行指定的秒数,该函数的原型通常在<unistd.h>头文件中定义

当程序运行起来,会不断打印“hello linux!”。

 这时我们再打开一个Xshell程序,就可以在不打扰上面进程的情况下输入新指令并运行。

 我们可以使用ps指令来查看进程信息。其中head -1表示拿到进程信息的第一行,grep加某些进程名,表示拿到该进程名那行信息。你会发现COMMAND这列中含有grep指令,这是因为gerp myproc本身就含有myproc,也会打印出来。想要去掉它,grep后加上-v grep,就是忽略grep相关的文本信息。

  • -a:显示所有终端下的进程。
  • -x:显示没有控制终端的进程(在后台运行)。
  • -j:显示与作业控制相关的信息。

我们观察上图,第一行中有许多属性名词,其中PID表示进程的唯一标识符,PPID表示父进程的标识符。myproc进程pid是4340。如果我们按下Ctrl+C按键,会中断该进程。

 当再次启动进程时,使用ps指令获取进程信息,pid值为6336。你会发现同样都是myproc的进程,pid值发生改变。因为系统使用一个累加的计数器维护进程的PID值,在你启动进程时,操作系统也不断在请求任务。所以,PID值出现变化且不连续是很正常的。

4.2 geipid函数获取PID

getpid是一个系统调用级函数,可以获取进程的id值。需要包含两个头文件,分别是unistd.h和sys/types.h。其中pid_t其实本质就是long int类型,只不过被typedef封装了一下。

使用getpid函数不用频繁获取,只需获取一次,因为一个进程启动之后,id值不会改变的,除非重新启动一次。当myproc程序执行起来后,可以看到进程id值是8042。我们使用ps指令获取myproc进程的信息,会发现pid值也是8042。

4.3 kill指令终止进程

如果你想要终止一个进程,可以在键盘上按下Ctrl + C的按键,发出终止信号。或者使用kill指令,使用kill -l可以查看kill指令的选项,其中9对应的选项就是终止进程。

终止进程操作如上图所示,输入kill + -9 + 进程id值,在另外一恶搞Xshell程序中可以看到myproc进程停止,显示了Killed信息。

4.4 进程信息文件夹

(1)exe

我们刚刚通过ps指令可以获取进程的一部分信息。在Linux操作系统下,一切皆文件。我们使用ls指令查看根目录,会发现有个proc目录。

proc就是process(进程)的缩写,再使用ls指令查看该目录,可以发现许多以数字命名的目录,这些就是某个进程的id值,里面存放的就是该进程的相关信息。

运行myproc程序后,生成一个pid值为19775的进程,使用ls命令查看/proc路径下的文件,其中就有名为19775文件夹。该文件夹存放的就是该进程的详细信息。

我们查看/proc/19775路径下的文件,会发现里面有许多内容。今天需要认识一下exe和cwd。进程是被某个可执行程序启动,exe存放的就是该可执行程序的路径

当我们再打开一个Xshell程序,进入到myproc的目录下,删除该可执行程序,你会发现进程还是在运行。因为进程已经被加载到内存当中,删除硬盘的可执行程序不会造成影响。但是再次查看/proc/19775路径下的文件,会发现exe显示该路径下的可执行程序已被删除。

(2)cwd

cwd全称是current working directory,意思是当前工作目录。我们在C语言中创建一个文件,如果没有指定绝对路径,默认生成在当前路径下。下面我们写个代码验证一下。

运行myproc程序,过几秒终止进程。查看当前目录,会发现多了一个file.txt文件。所以使用fopen新建一个文件时,不是你以为的相对路径,而是拿到cwd再加上你输入的文件名,形成一个绝对路径。还有什么方式可以证明上面的说法呢?

我们可以使用一个系统调用函数chdir,它的作用就是改变当前进程的工作目录。我们在myproc.c中使用chdir函数,修改当前工作目录为home目录下的普通用户,也就是我正在使用的用户。如果修改到根目录或者家目录,无法新建文件夹,因为普通用户没有写权限,只有超级用户才可以新建文件或者目录。

当我们启动myproc程序后,立即查看/proc/24404中内容,会发现cwd变成了普通用户的目录。再使用ls命令查看工作目录,发现有file.txt文件。这就验证了在代码中新建文件,如果不写绝对路径,会在输入的文件名前加上当前工作目录。

5. 进程PPID

5.1 getppid函数获取PPID

ppid是指parent process id,即某个进程的父进程id值。

其中getppid函数,是获取某个进程的父进程id值。

 修改一下myproc.c中的代码,获取一下父进程id,并打印出来。

 当我们启动myproc程序,运行一会后,终止该程序,再启动该程序,重复三次。你会发现它们的父进程id值都是19496。

我们使用ps指令查看该父进程信息,发现该进程的command(执行的指令)是-bash。其中bash是Linux系统下的命令行解释器,类似于windows系统中的cmd。

因此有个结论,在Linux操作系统启动之后,命令行上执行指令或者执行程序,本质上都是bash的进程创建的子进程,由这些子进程执行我们的代码

5.2 fork函数创建子进程

fork函数也是系统调用函数。函数原型如上,返回值类型是pid_t,不用传参。它的作用是在当前进程下创建一个子进程。

修改myproc.c中的代码,一开始打印一下当前进程的pid和ppid。之后调用fork函数,试着打印子进程的pid和ppid。

运行myproc程序,你会发现打印了三行语句。其中第一行语句是第一个printf函数打印的,pid值为29447的进程应该是myproc启动后的进程它的父进程就是上面提到的bash。

但是下面打印了两行语句,第二行语句的pid值为29447,ppid值是26892,应该是当前程序的进程。最后一行语句中,pid值是29448,ppid值是29447,它的父进程就是myproc程序启动后的进程,说明这是fork函数创建出来的子进程,并且pid值是连续的。

可以得出结论,调用fork函数,当前进程会创建一个子进程,这两个进程会同时执行后面的代码,相当于两个执行流分支。因为这两个进程是连续创建的,它们的pid值连续。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>int main()
{printf("I am a process, pid: %d, ppid: %d\n", getpid(), getppid());pid_t id = fork();if (id > 0){while (1){printf("我是父进程,pid: %d, ppid: %d, ret id: %d\n", getpid(), getppid(), id);sleep(1);}}else if (id == 0){while (1){printf("我是子进程,pid: %d, ppid: %d, ret id: %d\n", getpid(), getppid(), id);sleep(1);}}}

 fork函数调用成功,给父进程返回子进程的pid,给子进程返回0。如果调用失败,会设置错误值到errno变量中。

运行结果如上,fork创建进程后,当前进程接收到子进程的id值,子进程接收到0。那么为什么if else语句看起来能同时成立,且有两个返回值?

上面有提到,进程 = 内核数据结构(task_struct) + 程序的代码和数据。那么fork函数创建一个子进程时,操作系统会创建一个task_struct结构体对象。程序中的代码因为是只读的,子进程与父进程共享代码。

而子进程会私有一份数据,因为两个进程间数据互相修改,可能会触发某些条件导致程序崩溃,所以进程之间具有很强的独立性,一个进程崩溃不会影响另外一个进程。这就类似于手机上启动许多应用,如微信,抖音等,如果微信崩溃,不会影响抖音的运行。

因为fork函数创建子进程后,会出现两个执行流,那么此时fork函数返回一个值时,有两个执行流进行返回,并且进程间的数据是各自私有的,那么id变量会有两份。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>int g_val = 0;int main()
{printf("I am a process, pid: %d, ppid: %d\n", getpid(), getppid());pid_t id = fork();if(id > 0){while(1){printf("我是父进程,pid: %d, ppid: %d, ret id: %d, g_val: %d\n", getpid(),     getppid(), id, g_val);sleep(1);}}else if(id == 0){while(1){printf("我是子进程,pid: %d, ppid: %d, ret id: %d, g_val: %d\n", getpid(), getppid(), id, g_val);g_val++;sleep(1);}}}

 如上我们可以定义一个全局变量g_val,在父进程中只打印出来,在子进程中不仅打印出来,每次让g_val变量加1。

结果如上,父进程的g_val值一直是0,子进程的g_val值不断变化。这就证明了子进程会私有一份数据。

6. 创建多进程

#include <iostream>
#include <unistd.h>
#include <vector>
#include <sys/types.h>
using namespace std;const int num = 10;void SubProcessRun()
{while(true){cout << "I am a sub process, pid: "<< getpid() << " ,ppid: "<<getppid()<<endl;sleep(5);}
}int main()
{   vector<pid_t> childproc;for(int i = 0; i < num; i++){pid_t id = fork();if (id == 0){//子进程SubProcessRun();}//走到这里,只能是父进程执行childproc.push_back(id);}//父进程遍历所有子进程的pidcout << "我的所有子进程:";for(auto child: childproc){cout<<child << " ";}cout << endl;sleep(10);while(true){cout << "我是父进程,pid: "<< getpid()<<endl;sleep(1);}return 0;
}

使用一个for循环,使用fork创建子进程。每个子进程再调用SubProcessRun函数,死循环不断打印pid值。 使用vector数组存储子进程的pid值,然后遍历打印出来。父进程也执行个死循环,不退出。

运行该程序,就可以一次性创建多个连续进程。

7. fork函数如何返回两个值

fork函数为什么会返回两个返回值?

因为fork函数内部再返回值之前,会先创建子进程,创建进程会先在内核中创建task_struct结构体对象,用于管理进程的属性,然后代码共享,数据拷贝父进程的。这时,子进程已经开始运行,那么就会有两个执行流,最后返回值也是代码,就会被父子进程各自返回一次。

不过还有一个问题没有弄清楚,return语句返回的是一个值。为什么两个执行流返回时,这个值就变成两个不同的值?这是后面会解决的问题。


创作充满挑战,但若我的文章能为你带来一丝启发或帮助,那便是我最大的荣幸。如果你喜欢这篇文章,请不吝点赞、评论和分享,你的支持是我继续创作的最大动力!

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux驱动开发(速记版)--GPIO子系统

第105章 GPIO 入门 105.1 GPIO 引脚分布 RK3568 有 5 组 GPIO&#xff1a;GPIO0 到 GPIO4。 每组 GPIO 又以 A0 到 A7&#xff0c;B0 到 B7&#xff0c;C0 到C7&#xff0c;D0 到 D7&#xff0c;作为区分的编号。 所以 RK3568 上的 GPIO 是不是应该有 5*4*8160 个呢&#xff1…

MySQL高阶2004-职员招聘人数

目录 题目 准备数据 分析数据 实现 题目 一家公司想雇佣新员工。公司的工资预算是 70000 美元。公司的招聘标准是&#xff1a; 雇佣最多的高级员工。在雇佣最多的高级员工后&#xff0c;使用剩余预算雇佣最多的初级员工。 编写一个SQL查询&#xff0c;查找根据上述标准雇…

男单新老对决:林诗栋VS马龙,巅峰之战

听闻了那场激动人心的新老对决&#xff0c;不禁让人热血沸腾。在这场乒乓球的巅峰之战中&#xff0c;林诗栋与马龙的对决无疑是一场视觉与技术的盛宴。 3:3的决胜局&#xff0c;两位选手的每一次挥拍都充满了策略与智慧&#xff0c;他们的每一次得分都让人心跳加速。 林诗栋&am…

Linux自动化构建工具Make/Makefile

make是一个命令 makefile是一个文件 touch 创建并用vim打开makefile 写入依赖对象和依赖方法 mycode是目标文件 第二行数依赖方法 以tab键开头 make makefile原理 makefile中写的是依赖关系和依赖方法 clean英语清理文件 后不用加源文件。.PHONY定义clean是伪目标。 make只…

动态SLAM总结二

文章目录 Mapping the Static Parts of Dynamic Scenes from 3D LiDAR Point Clouds Exploiting Ground Segmentation&#xff1a;&#xff08;2021&#xff09;RF-LIO&#xff1a;&#xff08;2022&#xff09;RH-Map&#xff1a;&#xff08;2023&#xff09;Mapless Online …

[C++]使用纯opencv部署yolov11-pose姿态估计onnx模型

【算法介绍】 使用纯OpenCV部署YOLOv11-Pose姿态估计ONNX模型是一项具有挑战性的任务&#xff0c;因为YOLOv11通常是用PyTorch等深度学习框架实现的&#xff0c;而OpenCV本身并不直接支持加载和运行PyTorch模型。然而&#xff0c;可以通过一些间接的方法来实现这一目标&#x…

POLYGON Nature - Low Poly 3D Art by Synty 树木植物

一个低多边形资源包,包含可以添加到现有多边形风格游戏中的树木、植物、地形、岩石、道具和特效 FX 资源。 为 POLYGON 系列提供混合样式树这一新增功能。弥合 POLYGON 与更传统的层级资源之间的差距。还提供了一组经典的 POLYGON 风格的树木和植被以满足你的需求。 该包还附带…

系统安全 - Linux /Docker 安全模型及实践

文章目录 导图Linux安全Linux 安全模型用户层权限管理的细节多用户环境中的权限管理文件权限与目录权限 最小权限原则的应用Linux 系统中的认证、授权和审计机制认证机制授权机制审计机制 小结 内网安全Docker安全1. Docker 服务隔离机制Namespace 机制Capabilities 机制CGroup…

JavaWeb - 8 - 请求响应 分层解耦

请求响应 请求&#xff08;HttpServletRequest&#xff09;&#xff1a;获取请求数据 响应&#xff08;HttpServletResponse&#xff09;&#xff1a;设置响应数据 BS架构&#xff1a;Browser/Server&#xff0c;浏览器/服务器架构模式。客户端只需要浏览器&#xff0c;应用程…

Oracle中MONTHS_BETWEEN()函数详解

文章目录 前言一、MONTHS_BETWEEN()的语法二、主要用途三、测试用例总结 前言 在Oracle数据库中&#xff0c;MONTHS_BETWEEN()函数可以用来计算两个日期之间的月份差。它返回一个浮点数&#xff0c;表示两个日期之间的整月数。 一、MONTHS_BETWEEN()的语法 MONTHS_BETWEEN(dat…

水下声呐数据集,带标注

水下声呐数据集&#xff0c;带标注 水下声呐数据集 数据集名称 水下声呐数据集 (Underwater Sonar Dataset) 数据集概述 本数据集是一个专门用于训练和评估水下目标检测与分类模型的数据集。数据集包含大量的水下声呐图像&#xff0c;每张图像都经过专业标注&#xff0c;标明…

vSAN05:vSAN延伸集群简介与创建、资源要求与计算、高级功能配置、维护、故障处理

目录 vSAN延伸集群延伸集群创建延伸集群的建议网络配置vSAN延伸集群的端口见证主机的资源要求vSAN延伸集群中见证节点带宽占用vSAN延伸集群的允许故障数vSAN延伸集群不同配置下的空间占用 vSAN延伸集群的HA配置vSAN延伸集群的DRS配置vSAN存储策略以及虚拟机/主机策略的互操作vS…

华为最新业绩出炉!上半年营收4175亿元,同比增长34%!

华为2024年上半年经营业绩分析:稳健发展,符合预期 [中国,深圳,2024年8月29日] 今日,华为发布了其2024年上半年的经营业绩,整体表现稳健,结果符合预期。在复杂多变的全球市场环境下,华为凭借强大的创新能力和市场洞察力,实现了销售收入和净利润的显著增长。 上半年,华…

C语言:预编译过程的剖析

目录 一.预定义符号和#define定义常量 二.#define定义宏 三.宏和函数的对比 四、#和##运算符 五、条件编译 在之前&#xff0c;我们已经介绍了.c文件在运行的过程图解&#xff0c;大的方面要经过两个方面。 一、翻译环境 1.预处理&#xff08;预编译&#xff09; 2.编译 3…

Spring Boot 整合 Keycloak

1、概览 本文将带你了解如何设置 Keycloak 服务器&#xff0c;以及如何使用 Spring Security OAuth2.0 将 Spring Boot 应用连接到 Keycloak 服务器。 2、Keycloak 是什么&#xff1f; Keycloak 是针对现代应用和服务的开源身份和访问管理解决方案。 Keycloak 提供了诸如单…

Unity MVC框架演示 1-1 理论分析

本文仅作学习笔记分享与交流&#xff0c;不做任何商业用途&#xff0c;该课程资源来源于唐老狮 1.一般的图解MVC 什么是MVC我就不说了&#xff0c;老生常谈&#xff0c;网上有大量的介绍&#xff0c;想看看这三层都起到什么职责&#xff1f;那就直接上图吧 2.我举一个栗子 我有…

人工智能新闻和发展 (24001)- By 10/4/2024

1. 谷歌增强了搜索中的人工智能&#xff0c;允许对图像进行语音提问。 Google adding AI to answer voiced questions about images | AP NewsGoogle is pumping more artificial intelligence into its search engine. New features will enable people to voice questions a…

15分钟学 Python 第39天:Python 爬虫入门(五)

Day 39&#xff1a;Python 爬虫入门数据存储概述 在进行网页爬虫时&#xff0c;抓取到的数据需要存储以供后续分析和使用。常见的存储方式包括但不限于&#xff1a; 文件存储&#xff08;如文本文件、CSV、JSON&#xff09;数据库存储&#xff08;如SQLite、MySQL、MongoDB&a…

无神论文解读之ControlNet:Adding Conditional Control to Text-to-Image Diffusion Models

一、什么是ControlNet ControlNet是一种能够控制模型生成内容的方法&#xff0c;能够对文生图等模型添加限制信息&#xff08;边缘、深度图、法向量图、姿势点图等&#xff09;&#xff0c;在当今生成比较火的时代很流行。 这种方法使得能够直接提供空间信息控制图片以更细粒…

PCL 1.8.1 + VTK 1.8.0 + QT5.14.2+ VS2017 环境搭建

先看看效果: PCL 1.8.1下载安装: Tags PointCloudLibrary/pcl GitHub 安装完成后: 如果VTK想重新编译的,可以看我的这篇博客: