Linux进程概念
目录
1.冯诺依曼
2.冯诺依曼体系结构
3.内存与访问速度
4.CPU
5.一个简单的数据传输范例
二、什么是操作系统
1.为什么要有操作系统
2.操作系统如何管理软硬件
(1)操作系统管理软硬件的本质
(2)操作系统管理软硬件的方式
3.什么是系统调用
(1)什么是系统调用
(2)系统调用和库函数
三、进程的认识
1.进程的概念
2.进程控制块(PCB)
(1)认识进程控制块
(2)进程控制块的内容
四、进程的基本操作
1.查看进程
(1)查看全部进程
(2)查看指定进程
(3)另一种方式查看所有进程
2.创建进程
3.结束进程
(1)终止前台进程
(2)终止后台进程
(3)暂停和继续一个进程
(4)前台与后台进程
4.三个系统调用
一、冯诺依曼体系结构
1.冯诺依曼
如果你在大学上过程序设计的课,那么这个人的名字你一定不会陌生——冯·诺依曼。
约翰·冯·诺依曼(John von Neumann,1903年12月28日-1957年2月8日),美籍匈牙利数学家、计算机科学家、物理学家,是20世纪最重要的数学家之一。冯·诺依曼是罗兰大学数学博士,是现代计算机、博弈论、核武器和生化武器等领域内的科学全才之一,被后人称为“现代计算机之父”、“博弈论之父”。
1945年6月约翰·冯·诺依曼与戈德斯坦、勃克斯等人,联名发表了一篇长达101页纸的报告《First Draft of a Report on the EDVAC》,即计算机史上著名的“101页报告”。在报告中冯·诺伊曼明确提出了计算机的体系架构,也就是现在著名的冯诺依曼体系结构。
2.冯诺依曼体系结构
从1951年第一台电子计算机EDVAC开始,计算机经历了多次的更新换代,不管是最原始的、还是最先进的计算机,使用的仍然是冯·诺依曼最初设计的计算机体系结构。因此冯·诺依曼被世界公认为“计算机之父”,他设计的计算机系统结构,称为“冯诺依曼体系结构”。
它包含输入设备、存储器、CPU(中央处理器)、输出设备
- 输入设备:键盘,鼠标,扫描仪,写板等等。
- 输出设备:显示器,声卡,打印机等等。
- 中央处理器(CPU):运算器 + 控制器 + 其他(寄存器等)。
- 存储器:一般指内存,我们常说的C盘、D盘等硬盘并不属于存储器,而是属于输入或者输出设备
3.内存与访问速度
在计算机中,设备访问处理数据的速度是不同的,遵循:CPU >> 储存器 >> 外设
CPU的读取数据的速度很快,消耗的时间数量级在几纳秒(十的负九次方秒),而内存访问大概在百纳秒,磁盘访问消耗时间的数量级在几十毫秒。磁盘的读写速度比内存慢了几万倍,如果我们只是让CPU从磁盘读取数据,那么磁盘传输数据的速度远远小于CPU处理的速度,CPU在大部分时间都会闲置。
就如同,盖房子时垒墙的人一秒钟砌好一块砖,而递砖的需要一万秒才能给你递上一块砖,分别对应CPU和磁盘。
所以设计了内存用来弥补磁盘与CPU处理速度的巨大鸿沟,磁盘将数据传递到内存,内存再将数据传递给CPU,这样的分级数据传输让数据不断地流向快速处理数据的计算机组件,让计算机的运行效率大大提高。
到这里我们意识到,CPU直接与内存交互而不与磁盘交互。
4.CPU
存储器负责从外设读取数据来交给CPU,CPU将数据储存在寄存器内,CPU处理数据后,数据又会通过内存再写入外设。
CPU的执行速度很快,但是很笨。只能接收其他设备的指示,用别人的数据,计算别人的数据再将数据传回到储存器。
CPU接收到的指示就是我们写的代码,我们的代码保存在磁盘上,经过编译链接后会形成可执行程序。只有可执行程序,也就是二进制的机器码从磁盘上加载到内存上,再内存加载到CPU后才能开始执行。也就是说CPU只能运行自己可以识别的指令。
我们之前讲Linux时提到了操作系统的Kernel和Shell,Shell把我们的指令翻译成Linux能够理解的指令,然后操作系统,也就是核心Kernel会进行处理,然后讲结果传回Shell。CPU同样,它有自己的指令集,程序翻译过来的二进制机器码都是CPU可以看懂的指令集组成的。
5.一个简单的数据传输范例
假设张三要给李四发送一句你好(不考虑网络的具体工作情况),则两台电脑的数据流向:
张三电脑的数据流向:
(1)张三从键盘上输入你好,此时键盘这个外设上就有了数据。
(2)外设上的数据传递给了存储器,存储器就让CPU来处理这些数据。
(3)CPU从存储器上读取数据并且进行处理,然后再将处理后的数据写到存储器上。
(4)存储器再将数据写到网卡,网卡也是一个外设。
数据经过网络传递到李四那里
李四电脑的数据流向:
(1)李四电脑的网卡外设上受到了数据。
(2)外设上的数据传送给了存储器,存储器让CPU来处理这些数据。
(3)CPU从存储器上读取数据并且进行处理,然后再将处理后的数据写到存储器上。
(4)存储器再将数据写到显示器这个外设上。
二、什么是操作系统
首先给出操作系统的定义:操作系统是一个进行软硬件资源管理的软件。
1.为什么要有操作系统
你平时在使用电脑的时候,如果你去看你的任务管理器,你就算什么软件都不开,也会有大概一百个进程同时在运行,每一个进程又会传递数据给CPU,CPU又会将结果传回给程序。
这些进程的运行就需要操作系统的管理,可知操作系统可以管理软件。
你的显示屏、键盘、鼠标等这些铁疙瘩为什么能正常使用,这些都需要操作系统的有效管理才能实现。
这些设备的使用就需要操作系统的管理,可知操作系统可以管理硬件。
所以,操作系统存在的意义:通过合理管理软硬件资源,为用户提供良好的执行环境。
2.操作系统如何管理软硬件
(1)操作系统管理软硬件的本质
操作系统是一个管理者,他管理的内容本质上就是各种数据。
现实生活中也是如此,就比如在学校,校长是学校的管理者,二我们除了开学和毕业典礼大概率见不到校长。但是,如果你不好好学习,各种挂科,校长也是可以开除你的。校长没有见过你,但是却可以管理你,实质上就是通过我们的学号,姓名,电话,成绩等等信息来了解你现在的状况,进而管理你的。
而校长是通过辅导员,院级主任等各种渠道获得你的数据的,这些在你和校长中间的人都是中间执行者。这些中间执行者在管理硬件时就是驱动,操作系统管理硬件信息,指挥驱动运行对应的硬件。
(2)操作系统管理软硬件的方式
首先对于硬件:
操作系统将使用不同类型的结构体对象将这些硬件的信息保存起来,比如说对应键盘就创建一个保存键盘信息的结构体,鼠标就就创建一个保存鼠标信息的结构体,最后将所有的结构体对象使用链表等数据结构管理起来。
软件资源也是类似的:
在操作系统中,软件的运行都可以看作一个或多个进程,每一个进程都对应创建一个名为tast_struct的结构体,最后将所有的结构体使用链表等数据结构管理起来。
操作系统这个管理者管理的内容主要有四大块:进程管理,文件管理,内存管理,驱动管理。
结论: 系统在管理资源的时候,先描述,再组织。
不过对于Linux操作系统是用C语言写的,所以描述进程都是用结构体来保存资源的属性,然后用链表或者其他高效的数据结构组织起来,方便管理。一般而言,使用的都是链表。
3.什么是系统调用
(1)什么是系统调用
系统调用:系统提供的接口称为系统调用接口,俗称系统调用。
操作系统的管理保障计算机中软硬件的正常运行,用户才能正常使用计算机。如果用户直接与操作系统进行交互,对于操作系统来说非常不安全,如果用户在操作系统中乱操作,会导致系统崩溃。对于用户来说,需要对操作系统非常深入的了解才能够直接操作它,而且操作系统也不便于直接操作。但是,有时候我们还是需要获取操作系统中的一些信息,最简单的就是任务管理器,查看内存占用情况等。
于是操作系统提供了一些调用接口来供用户使用,这些接口和我们平时调用的函数差不多,只是此时它是系统接口,操作的是系统。这些系统调用相当于对操作系统的一些功能的封装,就如同哦我们去银行取钱,我们会通过柜台或者ATM机,而不是直接到银行的金库里直接拿走现金。
在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。操作系统给你一个安全的渠道去获取操作系统内部的信息,以免干扰自身的工作。
(2)系统调用和库函数
上图是计算机的软硬件体系结构示意图。
图中可以看到,用户可以通过shell外壳进行系统调用,比如pwd指令,在屏幕上打印出当前在哪个目录下。一些库函数,比如printf,都是通过系统调用来与硬件进行交互。printf能把内容打印到屏幕上也是应用了系统调用才能让程序与硬件交互。
系统调用在使用上,功能比较基础,对用户的要求相对也比较高。所以,有心的开发者可以对部分系统调用再次进行适度的封装,从而形成库,库内部的函数就是库函数,库的出现有利于上层用户的使用与操作系统本身的保护。
三、进程的认识
1.进程的概念
进程的概念:正在执行的程序。
程序源文件,在经过编译链接后会生成可执行程序,但此时可执行程序是存放在磁盘也就是外设上的。而程序要运行的时,可执行程序会从硬盘加载到内存上,就是将磁盘上的内容拷贝到内存中。
在可执行程序加载到内存中时,操作系统就会创建一个名称为task_struct的结构体,来描述可执行程序的各种信息,并且将这个结构体对象放入操作系统维护的数据结构中。
当可执行程序加载到内存中,对应的结构体对象也被插入到相应的数据结构中后,此时磁盘中的程序文件就被加载到内存中变成了一个进程。
所以说,进程 = 内核数据结构 + 对应的可执行程序代码。
同样其他的可执行程序被加载到内存中,操作系统也会一一对应地创建结构体对象,并且把它们都插入到相应的数据结构中来管理。每一个进程都需要经过CPU的处理,CPU执行进程的指令时,CPU会根据进程对应的结构体对象的内容,找到已经被加载到内存中的可执行程序去执行。
进程是动态的,具有动态属性。
操作系统通过管理进程对应的结构体对象达到管理进程的目的。
2.进程控制块(PCB)
(1)认识进程控制块
概念:用来描述进程信息的数据结构,可以理解为进程属性的集合。
用来描述进程各种属性进行的结构体,在Linux操作系统下这个结构体名字是task_struct,PCB是存储进程数据的结构体的统称,因为在其他系统中可能描述进程的结构体名称就不再是task_struct,但是起到的作用一致。
结构体的代码如下(部分,全部代码有两百多行):
不管它有多长,一定会包含以下信息:
struct task_struct
{
//进程的各种属性
//进程对应的代码位置
struct task_struct* next//下一个结构体节点的地址,用链表串联起来
}
每一个进程对应的结构体对象通过链表连接起来,每一个节点就是一个PCB,而在Linux中是这个PCB是task_struct
(2)进程控制块的内容
tast_struct结构体中的内容大致有如下几类:
- 示符: 描述本进程的唯一标示符,用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器: 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针.
- 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
- I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
- 其他信息
这些具体的内容不需要详细了解,这里只是认识一下就可以了。
四、进程的基本操作
1.查看进程
(1)查看全部进程
通过ps ajx查看正在运行的所有进程
指令:ps ajx
功能:显示运行中的进程的简要信息
我们查看进程信息时,第二行最上面有一个PID,这个PID值可以看作进程的编号。PID值不同,进程就不同;PID值相同,一定是一个进程。可以说,PID是一个进程的唯一标识符,是用来识别一个进程的。
(2)查看指定进程
ps ajx也可以使用管道组合其他指令来查看指定进程的信息
指令:ps ajx | grep 进程名
功能:查看筛选出来的进程的信息。
我输入了ps ajx | bash ,所有含bash的进程就被打印出来了
你也可以通过逻辑运算符同时查找多个对应名字的进程
指令:ps ajx | head -数字 && ps ajx | grep 程序名
功能:将多个指令组合在一起,显示出进程的信息,其中ps ajx | head -数字表示打印出打印所有元素的列表的前三行的内容
比如说,ps ajx | head -3 && ps ajx | grep bash,就相当于先打印出打印所有元素的列表的前三行的内容,然后再打印含有bash的进程,两个部分相互独立,执行完一个再执行另一个。
(3)另一种方式查看所有进程
还有一种方法来查看进程,了解即可。
指令:ls /proc
功能:查看系统上当前运行的进程,proc是专门用来放进程信息的文件。
2.创建进程
首先,我们右键单击Shell的对话框,点击复制会话。
然后,把新的会话拖动到右侧,如下图所示即可
我们在test.c中编写一段代码,然后编译运行。
我们就创建了一个进程./test,我们用Ctrl+c终止程序的运行,此时这个进程也消失了。
3.结束进程
(1)终止前台进程
指令:Ctrl + C
功能:结束了一个正在运行的前台进程
Ctrl+c让此时正在运行的进程结束,但只有在前台运行的进程才能用这种方法终止。
(2)终止后台进程
指令:kill -9 进程PID
功能:杀掉PID值对应的进程
Ctrl+c可以终止前台进程,但kill -9可以终止后台和前台的进程,我们再次复制一个对话窗口并在左侧窗口运行一个test的可执行程序
程序运行时我们在右侧窗口查找相应的进程,其中第二个就是我们现在运行的进程
前面的一串数字前两个分别对应PPID(当前进程父进程的编号)和PID(当前进程的编号),当我们需要杀掉一个进程时输入的是PID,也就是第二个。
我们可以看到左边的程序也显示killed并停止运行了
(3)暂停和继续一个进程
我们通常用kill -19暂停一个正在运行的进程
指令:kill -19 进程PID
功能:暂停一个正在运行的进程
运行test可执行程序,在左侧查找test进程
输入指令暂停进程,下面就提示暂停了
在下面我们可以输入Linux指令
指令:kill -18 进程PID
功能:继续运行一个被暂停的进程
我们输入指令让其继续运行
此时进程也开始继续运行
(4)前台与后台进程
后台程序基本上不和用户交互,优先级别低。前台的程序和用户交互,需要较高的响应速度,优先级别高。
在Linux系统中前后台程序的可以通过中间的进程状态判断,也就是1002数字前面的字母S,我们目前不需要知道S表示什么状态。只需要知道,字母后面有加号的是前台进程,没有加号的是后台进程。
前面说过,Ctrl+c不可停止后台运行的进程,而使用kill -9既能停止的前台进程,也可以停止后台程序。我们可以做个小实验。
上一个例子中暂停后再次开始运行是已经变成了一个后台程序,此时我们在左侧的对话框内输入Ctrl+c已经不再管用,只能输入kill -9才能终止
4.三个系统调用
系统调用接口:getpid()
头文件:sys/types.h
功能:获得当前进程的PID
系统调用接口:getppid()
头文件:sys/types.h
功能:获取当前进程父进程的PID
在代码中可以使用系统调用获得当前进程的PID和父进程的PID,进程在运行时就会打印出当前进程的PID和父进程的PID。PID值是一个整型数据,使用%d打印即可
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
int i = 0;
while(1)
{
printf("我是一个进程,我的PID是:%d,我的父进程的PID是:%d\n", getpid(), getppid());
i++;
sleep(1);
}
return 0;
}
这段代码会不断打印自己的PID和它的父进程的PID,每次打印睡眠一秒。我们在左侧会话运行这个程序,这个程序就变成了一个进程,右侧查找进程,发现的确获取了PID
我们终止掉这个进程,再次运行这段代码
在再次运行代码时我们发现进程PID的值变了,由32387变成了847,但父进程的PID没有变化。
当一个进程结束以后,操作系统就会将它的PCB删除,此时内存中就没有这个进程的信息了。我们再运行代码时,代码会再次加载到内存中,操作系统会创建新的PCB来管理这个运行的进程,也就是说这是一个新的进程,原本PID当然会改变。
那么这个不变的父进程是谁呢?我们可以通过父进程的PID查找。
此时我们找到了一个进程,名字叫bash,说明我们刚才运行的进程都是由bash产生的。这个bash就是我们之前讲到的shell外壳,也就是我们与操作系统交互的中介,它也是一个进程,我们执行的指令,运行的代码基本上父进程都是它。
系统调用接口:fork()
头文件:unistd.h
功能:在执行完fork以后,存在两个进程,一个父进程一个子进程。
通过man指令来查看fork系统调用
fork是用来创建一个子进程的,fork很像我们C语言中的函数,但是仔细阅读你会发现,fork的返回值有两个。
子进程创建成功,fork的返回值有两个,一个是子进程的PID,这个值会给父进程,还有一个值是0,这个值给子进程。创建失败,直接返回-1给父进程。
除了Python的函数可以有两个返回值,C、C++还是Java等主流编程语言的函数返回值只能有一个,如果向返回多个值,可以返回一个值并把其他的值通过传递指针和引用的方式带出函数。
我们运行下面这段代码
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
int num = 1;
pid_t id = fork();
if (id < 0)
{
perror("进程创建失败\n");
return 1;
}
else if (id == 0)
{
int i = 1;
while(1)
{
printf("我是子进程,我的PID是:%d,我的父进程的PID是:%d,%d", getpid(), getppid(), i);
i++;
sleep(1);
}
}
else if (id > 0)
{
int j = 1;
while(1)
{
printf("我是父进程,我的PID是:%d,我的父进程的PID是:%d,%d", getpid(), getppid(), j);
j++;
sleep(1);
}
}
return 0;
}
运行后我们发现,父进程和子进程都打印出来自己的PID和父进程的PID
对于一个分支语句而言,只能进入其中一个语句框内,而这里却同时打印了id==0和id>0的内容,再编程语言中这样的情况是不可能发生的。
对于这个极其怪异的现象,我们可以这样理解:fork之后的代码是父进程和子进程共享的。
fork运行之后,子进程被创建,父进程也是同时运行。由于fork有两个返回值,子进程的PID值传递给父进程,是一个大于0的数,进入id>0的分支语句中。将0传递给创建的子进程,进入id==0的分支语句中,这样子进程与父进程才能同时打印。
阿J~: 最近我也在学习写博客,有空来看看我呀,一起互相学习。期待你的关注与支持
ltt不会写代码: 例子很好,非常的通俗易懂,太nice了
聪明的骑士: 而且建议你使用vs学习,devc++太老了
聪明的骑士: devc运行时可以把注释删掉
程度小袁猴: 博主nb大爱好文