【Linux】进程周边001之进程概念


风晓
风晓 2024-01-05 11:51:56 52764 赞同 0 反对 0
分类: 资源
前言 上篇文章我们说学习系统我们要翻越三座大山:进程周边、文件周边以及线程周边。
 
 
那今天我们就对第一座大山:进程周边开启攀登之旅💪
 
本篇文章主要讲解有关进程的基本概念,以及Linux系统下是如何管理进程的,还记得学习管理的六字真言么?没错,对于进程的管理也是先描述,再组织。
 
之后我们再来学习下如何查看进程以及进程的标识符PID、父进程的标识符PPID。
 
最后我们初步的认识下fork函数,并利用fork函数实现创建子进程等。
 
话不多说,直接进入我们今天的学习✍
 
欢迎大家📂收藏📂以便未来做题时可以快速找到思路,巧妙的方法可以事半功倍。
 
=========================================================================
 
GITEE相关代码:🌟fanfei_c的仓库🌟
 
=========================================================================
 
1.基本概念
较为官方的说法:
 
课本概念: 程序的一个执行实例,正在执行的程序等。
内核观点: 担当分配系统资源(CPU时间,内存)的实体。
辅助理解:
 
对代码进行编译链接产生的文件我们称之为可执行程序(.exe),执行该程序,该程序会被加载到内存中,此时便称之为进程。
 
2.描述进程-PCB
上节课我们学习了管理的概念,并总结为六字真言:先描述,再组织。
 
那对于操作系统来说,管理进程的方式仍然归结于这六字真言中。
 
我们也可以将进程描述起来,描述得到的就是进程控制块PCB(process control block)。
 
PCB就是进程属性的集合(数据结构),里面存储的是进程信息。
 
管理不是直接管理人,而是管理人的信息;
 
管理不是直接管理进程,而是管理进程的信息(PCB)。
 
此时操作系统对进程的管理就转化为对PCB对象的管理。
 
那对于某个数据结构的管理我们是很熟悉的,假如我们利用链表的方式进行组织,那对于进程的管理说白了就是对链表的增删查改。
 
 
 
换句话说:进程=PCB(内核数据结构)+可执行程序 
 
未来,所有对进程的控制和操作,都只和进程的PCB有关,和进程的可执行程序没有关系。
 
如果愿意,你可以把PCB(Node节点)放到任意数据结构中去。
 
2.1task_struct-Linux中的PCB
task_struct就是在Linux中描述进程的结构体(Linux是C语言编写)。
 
你可以理解为PCB是操作系统学科抽象的叫法,而在Linux中具体为task_struct。
 
即task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含进程的信息。
 
2.2task_struct内容分类(成员)
标示符(PID): 描述本进程的唯一标示符,用来区别其他进程(每次启动都会变化)。
状态: 任务状态,退出代码,退出信号等。
优先级: 相对于其他进程的优先级。
程序计数器(pc): 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
上下文数据: 进程执行时处理器的寄存器中的数据。
I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟总和,时间限制,记账号等。
其他信息。
3.查看进程
3.1通过系统目录查看
根目录下的proc目录,/proc下存储着进程信息。
 
 
 
目录名为数字的即为进程信息的目录,每个目录内存储着他们对应的进程信息。
 
而这些数字对应着该进程的标识符PID。
 
比如查看标识符PID=1的进程信息:
 
 
 
当我们新建一个普通的进程,并进入该进程所在目录时:
 
 
 
我们可以利用chdir系统调用接口修改工作目录。
 
 
 
3.2通过用户级工具ps查看
实例:ps ajx/ps aux
 
该命令可以查看所有系统进程。
 
 
 
 
 
现在我们来写一段代码并生成可执行程序,执行后变成进程我们如何查看呢?
 
(1)代码:
 
#include<stdio.h>
#include<unistd.h>                                                                                                                  
 
int main()
{
while(1)
{
printf("I am a process!\n");
sleep(1);
}
return 0;
}
(2)编译后执行:
 
 
 
 (3)打开多窗口方便我们观察
 
 
 
 (4)如何查看单个进程?
 
首先我们已经知道如何查看系统中所有进程了,即ps ajx,那我们可以利用Linux之前学习的一些指令来显示我们想要查看的单个指令。
 
实例:ps ajx | head -1 && ps ajx | grep test
 
对以上指令的解释:
 
 
 
我们来观察一下是否是我们想要的结果:
 
 
 
 我们发现:test进程利用刚才的指令我们得到了该进程的相关信息,但是黄色框内是什么?
 
其实是grep命令:
 
 
 
 这里也侧面反映出几乎所有独立的指令,就是程序,运行起来也是进程。
 
这里grep实际也是进程,且该进程内包含有test的信息,所以也显示出来了。
 
如何去掉这多余信息呢?
 
实例:ps ajx | head -1 && ps ajx | grep test | grep -v grep
 
-v选项是反向搜索的意思,即过滤掉包含有grep内容的信息。  
 
 
 
另外我们也可以通过指令对进程进行检测,检测他是否运行:
 
实例:while :; do ps ajx | head -1 && ps ajx | grep test | grep -v grep; sleep 1;done
 
观察进程创建和销毁的过程: 
 
 
 
 所以我们发现:进程是有生命的!
 
4.通过系统调用获取进程标识符(PID)
4.1PCB是属于操作系统的还是属于进程的?
答案是属于操作系统的,虽然PCB记录的是进程的相关信息,但是PCB是由操作系统创建并维护的。
 
那既然PCB是属于操作系统的,那我们如何查看PCB的信息呢?
 
在操作系统的那篇文章中我们提到过用户想要获取操作系统的信息,需要调用系统接口。
 
所以获取进程标识符(PID)等PCB的信息我们需要通过系统调用来获得,所以我们来认识下getpid()。
 
首先我们利用man getpid查看下命令手册:
 
 
 
我们发现getpid是在2号手册中,利用man man我们知道2号手册中记录的就是系统调用接口。 
 
 4.2使用getpid和getppid
(1)编写代码:
 
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
pid_t id = getpid();
while (1)
{
printf("I am a process!pid:%d\n", id);
sleep(1);
}
return 0;
}
(2)执行可执行程序并观察
 
 
 
那我们再来学习一下getppid(获取父进程的进程标识符)。
 
一般在Linux中,普通进程,都有他的父进程。
 
每一个子进程都是由父进程创建出来的。 
 
子进程只能有一个父进程,父进程可以有多个子进程。
 
每次执行可执行程序,进程标识符会改变(因为每次都是新的进程)。 
 
 
 
那我们来观察一下他的父进程:
 
(3)编写代码:
 
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
pid_t id = getpid();
    pid_t fid = getppid();
while (1)
{
printf("I am a process!pid:%d ppid:%d\n", id, fid);
sleep(1);
}
return 0;
}
(4)执行可执行程序并观察 
 
 
 
我们来查询一下该进程的父进程究竟是什么? 
 
 
 
我们发现该进程的父进程是bash(命令行解释器)。
 
在命令行启动的进程都是bash的子进程。
 
5.通过系统调用创建进程(fork初识)
./+可执行程序的方式是一种手动创建进程的方式。fork则是通过系统调用创建进程。
 
5.1fork函数创建子进程
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
 
int main()
{
printf("before fork : I am a process,pid:%d,ppid:%d\n", getpid(), getppid());
 
fork();
 
printf("after fork : I am a process,pid:%d,ppid:%d\n", getpid(), getppid());
sleep(2);
return 0;
}
现象:
 
 确实如我们所料,fork执行后,创建出了一个子进程。
 
 
 
并且我们发现fork后面的语句执行了两次。
 
也就是说fork之后,代码共享,从一个进程分为两个分支,一为父,一为子。
 
 
 
 那我们如何知道谁是父谁是子呢?
 
这就要研究一下fork函数的返回值问题了。
 
5.2fork的返回值
 
 
意思是返回给父进程的是子进程的PID,返回给子进程的是0.
 
为什么?
 
因为父:子= 1:n,子找父是很容易的,而父找子必须有子的pid。
两个返回值么?
 
我们来验证一下:
 
 
 
执行结果: 
 
也就是说父进程使用该变量就返回子进程的pid,子进程使用就返回0。
 
提问:fork函数为什么会返回两次? 
 
当一个函数运行到了最后执行return的时候,这个函数的核心逻辑已经执行完成了!
 
而fork函数中必然会有创建子进程这一操作,所以在fork函数返回值之前,子进程已经存在了。
 
所以fork函数会返回两次值写入到变量中。
 
提问:id怎么可能同一个变量既等于0又等于pid? 
 
一个进程崩溃会不会影响其他进程呢?答案是不会。
 
注:任意进程之间是具有独立性的,互相不能影响,即便是父子进程。
 
子进程被创建时,会继承大部分父进程的属性,即子进程的创建是以父进程为模板的。
 
模拟场景:父进程或子进程对一共享数据进行修改会发生什么?
 
前面我们刚说到任意进程之间具有独立性,互相不能影响,所以操作系统必须保证这一点。
 
假如为子进程修改该数据:子进程会从父进程那拷贝一份到自己这里进行修改,这一行为称之为写时拷贝。
 
父进程修改该数据也如此。
 
id就是这一共享数据,返回的本质就是写入。
 
linux中可以使用同一变量名,表示不同的内存。
提问:我们创建子进程的目的是什么?
 
一般而言:我们想让父子做不同的工作。
 
所以我们就可以利用返回值的不同使用if进行分流。
 
5.3使用if进行分流
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int ret = fork();
if (ret < 0) {
perror("fork");
return 1;
}
else if (ret == 0) { 
        //child的工作代码段
}
else { 
//father的工作代码段
}
sleep(1);
return 0;
}

如果您发现该资源为电子书等存在侵权的资源或对该资源描述不正确等,可点击“私信”按钮向作者进行反馈;如作者无回复可进行平台仲裁,我们会在第一时间进行处理!

评价 0 条
风晓L1
粉丝 1 资源 2038 + 关注 私信
最近热门资源
银河麒麟桌面操作系统备份用户数据  130
统信桌面专业版【全盘安装UOS系统】介绍  128
银河麒麟桌面操作系统安装佳能打印机驱动方法  120
银河麒麟桌面操作系统 V10-SP1用户密码修改  108
麒麟系统连接打印机常见问题及解决方法  27
最近下载排行榜
银河麒麟桌面操作系统备份用户数据 0
统信桌面专业版【全盘安装UOS系统】介绍 0
银河麒麟桌面操作系统安装佳能打印机驱动方法 0
银河麒麟桌面操作系统 V10-SP1用户密码修改 0
麒麟系统连接打印机常见问题及解决方法 0
作者收入月榜
1

prtyaa 收益393.62元

2

zlj141319 收益218元

3

1843880570 收益214.2元

4

IT-feng 收益210.13元

5

风晓 收益208.24元

6

777 收益172.71元

7

Fhawking 收益106.6元

8

信创来了 收益105.84元

9

克里斯蒂亚诺诺 收益91.08元

10

技术-小陈 收益79.5元

请使用微信扫码

加入交流群

请使用微信扫一扫!