🧑💻作者: @情话0.0
📝专栏:《Linux从入门到放弃》
👦个人简介:一名双非编程菜鸟,在这里分享自己的编程学习笔记,欢迎大家的指正与点赞,谢谢!
前言
那在还没有学习进程之前,就问大家,操作系统是怎么管理进行进程管理的呢?很简单,先把进程描述起来,再把进程组织起来!
一、什么是进程?
我们之前任何启动并运行程序的行为,都是操作系统帮助我们将程序转化为进程,完成特定的功能。
1.1 进程初探索
比如说咱们写了一段代码,并生成了一段可执行程序存放在磁盘中,可执行程序的本质就是一个普通的二进制文件,包括文件内容和文件属性。
文件内容: 自己写的代码以及相应的数据
文件属性:文件的创建时间、权限、类型等等
在Linux中,当我们生成一段可执行程序(a.out)并通过 ./ 的方式将其加载到内存当中,之后CPU便可从内存中拿到该文件的代码和数据执行。那么把代码和数据放到内存中就是进程了吗?
举个例子:我们把磁盘比作社会,内存比作学校,那么怎么一个人就可以称为一个学校的学生呢?难道就从社会上找一个人把他放到学校里面就成为了该学校的学生了吗?当然不是,学校里面还有食堂阿姨和保安大叔呢,难道他们也是学生?所以说,你是不是学生最关键的是你的基本信息在不在学校的学籍管理系统中,也就意味着学校能不能管理你。
1.2 进程管理的建模
当然,在磁盘当中肯定有多个执行程序,它们对应的数据和代码也都被加载到内存当中,那么操作系统就一定要想办法将这些数据和代码管理起来,也就是操作系统要将进程管理起来。那如何管理呢?先描述,再组织。
因此每当一个可执行程序加载到内存中时,在操作系统内核里都要为每一个进程在加载到内存之时操作系统都要在内核里创建一个数据结构对象。操作系统里叫 PCB,在 Linux 中叫 task_struct,task_struct 是 PCB的一种。在这个结构体里包含着关于这个进程的所有属性,当然肯定包含一个指针,指向内存中的数据和代码,每当加载一个可执行程序到内存中,操作系统都会为其创建一个task_struct结构体。这样的过程就像当时先描述。
再组织就是理解为在task_struct的属性里面再有一个结构体指针将每个结构体连接起来。当一个进程时间片走完就通过遍历的方式找下一个优先级高的进程交给CPU执行。
1.3 task_ struct 的内容
标识符: 描述本进程的唯一标示符,用来区别其他进程。
状态: 任务状态,退出代码,退出信号等。
优先级: 相对于其他进程的优先级。
程序计数器: 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
其他信息
1.4 概念
进程:内核关于进程的相关数据结构+当前进程的数据和代码。
二、进程属性
2.1 查看进程
#include <stdio.h>
#include <unistd.h>
int main()
{
while(1)
{
printf("process running!\n");
sleep(1);
}
return 0;
}
-
当我们写了这样的程序并运行,我可通过这样的方式查看进程的相关信息:
ps axj | head -1 && ps axj | grep myprocess
主要我们现在就是可以看到这个进程的PID为15560,当将这个代码执行两次(看下图)发现就会出现两个不同的进程,PID分别为16187和161198,这就是把这段代码和数据从磁盘中拿了两次到内存中,各自有各自的task_struct。 -
除了用ps这样的指令查看已经启动起来的进程之外,还可以通过这种方式来查看:
ls /proc
—查看根目录下的proc目录。
通过上图可以看到当前两个进程PID分别为19645和19650,而它们对应地出现在了proc的目录下,也就是说,每当创建好了一个进程之后,系统就会自动的在proc的目录下创建一个文件夹,以新增进程的PID命名。
可以就进入到对应的目录下cd /proc/19645
,此时就能查看到相关的属性信息,大多数其实我们都不太认识,有两个还是知道的,我已经框出来了,分别是指定文件的路径(exe)和可执行程序对应的路径(cwd)。
若此时将这个进程终止掉,那么系统中就不会存在进程ID值。
而在这进程目录下也是无法查看任何信息的。
2.2 通过系统调用获取进程标识符
我们区分不同的进程主要是看它们的进程标识符(PID),因为进程标识符可以唯一的标识一个进程。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
while(1)
{
printf("process running!, pid:%d\n",getpid());
sleep(1);
}
return 0;
}
可以通过geipid()
的方式获取某个进程的PID,并且每当ctrl+c
结束进程并重新执行该程序,它的PID都是不同的,这就相当于你考上了一个大学,有自己的学号,但是你不满意退学重新考了一年,结果又来到了这个学校,那当然你的学号是新的,不会因为你曾经来过就会将这个学号一直留给你。
getpid()
是获取当前进程的PID,而getppid()
是获取当前进程的父进程的PID。
printf("process running!, pid:%d, ppid:%d\n",getpid(),getppid());
但是发现了一个问题,为什么每次叉掉进程重新执行PID是变化的,但是PPID不变,这是为什么,而这个进程的父进程是什么呢?
结论:
- 我们发现它的父进程的名字叫bash,它是一个命令行解释器,本质是一个进程。
- 命令行启动的所有程序,最终都会变成进程,而该进程对应的父进程都是bash。
2.3 创建进程
我们写的代码在运行时就变成了一个进程,但是系统担心代码有错误于是让bash将你的代码创建为自己的子进程,哪怕你的代码有错误只会影响bash而不会影响系统,可是bash是通过怎样的方式创建的进程呢?
在Windows环境下,我们点击.exe
文件或双击快捷方式,系统都会创建相应的进程;在LInux环境下,我们 ./
可执行程序就是将程序变为进程去运行。
那么可以在代码层面创建子进程呢?当然有的:fork()
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
printf("1111111111111111111111111\n");
fork();
printf("2222222222222222222222222\n");
return 0;
}
运行这段代码发现 “222222…” 被打印了两次,这也就说明了这行代码被执行了两次。
printf("2222222222222222222222222 pid:%d, ppid:%d\n",getpid(),getppid());
通过这行代码发现这两行代码是被不同(两个)的进程执行的,下面进程的PID为16433,PPID为16432;上面进程的PID是16432,说明了上面对应的进程是下面对应进程的父进程。
当我们在 “111111…” 也打印出对应的PID和PPID,发现这一行代码是父进程执行的。在这里面就出现了父子进程,而这个里面父进程的父进程是谁呢?bash。这也就相当于出现了爷孙三代!同时也证明了在fork之后出现了两个执行流,一个是父进程的,一个是子进程的。至于父进程先执行还是子进程先执行完全取决于操作系统,并没有固定的先后顺序。
这是关于fork函数返回值的介绍:如果成功,则在父进程中返回子进程的PID,子进程中返回0。如果失败,在父进程中返回-1,没有创建子进程,并且errno被适当地设置。
pid_t ret = fork();
printf("2222222222222222222222222 pid:%d, ppid:%d,ret:%d,&ret:%p\n",getpid(),getppid(),ret,&ret);
在这里有两点疑惑:
- 一个函数为什么会有两个返回值呢?
- 相同的地址里存放的数据为什么不一样呢?
现在可针对于第一个问题作出回答:就是因为在fork之后出现了两个执行流,而这两个执行流是同时进行的,互不干扰的。frok 之后的代码是共享的,通常是通过 if 和 else if (fork函数的返回值)来进行分流的。
总结:文章来源:https://www.toymoban.com/news/detail-530062.html
-
fork做了什么?
每一个进程都有自己的内核数据结构(PCB)和进程的代码、数据。当fork了之后,操作系统会为子进程创建一份PCB,但是代码和数据父子进程是共享的,两个PCB会指向同一份代码数据。 - fork如何看待代码和数据呢?
进程在运行的时候,是具有独立性的,就相当于你电脑关掉了网易云音乐,但是并不会影响你的QQ。而父子进程在运行的时候也是一样的,当它两同时运行时你杀掉其中一个进程不会影响另一个进程。你可以做这样的实验,定义一个全局变量,在父进程里对这个变量重新赋值。根据打印的结果可以发现会打印出两个不同的数值。
对于进程的数据和代码来说,代码是只读的;数据是当一个执行流尝试修改数据的时候,操作系统会自动给当前进程触发写时拷贝,当要对数据修改时重新在内存申请一块空间存放修改后的数据而不去影响原来的数据。 - 如何理解两个返回值问题?
每当一个函数准备执行 return 的时候,这个函数的主体功能已经完成。所以在执行 fork 函数时,操作系统会先给子进程创建PCB,然后将其放在某个队列或链式结构中让操作系统进行管理。当主体功能完成时就要执行 return 语句,此时就会走向两个执行流并返回两个值。
总结
以上算是对进程相关知识的初步认识,后续还有更多内容继续学习,感谢大家支持并希望大家也可以根据这篇博客提出一些宝贵的意见或者讨论看法。文章来源地址https://www.toymoban.com/news/detail-530062.html
到了这里,关于【Linux从入门到放弃】进程概念、查看进程、创建进程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!