✨✨✨学习的道路很枯燥,希望我们能并肩走下来!
文章目录
目录
[一 冯诺依曼体系结构](#一 冯诺依曼体系结构)
[二 操作系统(Operator System)](#二 操作系统(Operator System))
[2.1 概念](#2.1 概念)
[2.2 设计OS的目的](#2.2 设计OS的目的)
[编辑 2.3 OS如何进行管理](#编辑 2.3 OS如何进行管理)
[编辑2.4 总结](#编辑2.4 总结)
[三 进程的标示符](#三 进程的标示符)
[3.1 基本概念](#3.1 基本概念)
[3.2 查看进程](#3.2 查看进程)
[3.2.1 ps ajx指令查看所有进程](#3.2.1 ps ajx指令查看所有进程)
[3.2.2 /proc](#3.2.2 /proc)
[编辑3.3 通过系统调用获取进程标示符](#编辑3.3 通过系统调用获取进程标示符)
[3.3.1 系统调用接口getpid 和getppid](#3.3.1 系统调用接口getpid 和getppid)
[编辑 3.2.2 理解ppid](#编辑 3.2.2 理解ppid)
[3.4 通过系统调用创建进程-fork(重要)](#3.4 通过系统调用创建进程-fork(重要))
[四 进程的状态](#四 进程的状态)
[4.1 进程的状态](#4.1 进程的状态)
[4.2 Linux中的进程状态](#4.2 Linux中的进程状态)
[编辑 五 进程优先级](#编辑 五 进程优先级)
[5.1 基本概念](#5.1 基本概念)
[5.2 PRI and NI](#5.2 PRI and NI)
[5.3 用top命令更改已存在进程的nice](#5.3 用top命令更改已存在进程的nice)
[5.4 其他概念](#5.4 其他概念)
[六 进程的调度切换](#六 进程的调度切换)
前言
本篇详细介绍了进一步介绍Linux中的进程,让使用者对进程有更加深刻的认知,而不是仅仅停留在表面,更好的模拟,为了更好的使用. 文章可能出现错误,如有请在评论区指正,让我们一起交流,共同进步!
一 冯诺依曼体系结构
我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系。
截至目前,我们所认识的计算机,都是有一个个的硬件组件组成
◎ 输入单元:包括键盘, 鼠标,扫描仪, 写板等
◎ 中央处理器(CPU):含有运算器和控制器等
◎ 输出单元:显示器,打印机等
关于冯诺依曼,必须强调几点:
● 这里的存储器指的是内存 不考虑缓存情况,
● 这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)
● 外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。
● 一句话,所有设备都只能直接和内存打交道。
对冯诺依曼的理解,不能停留在概念上,要深入到对软件数据流理解上,请解释,从你登录上qq开始和某位朋友聊天开始,数据的流动过程。从你打开窗口,开始给他发消息,到他的到消息之后的数据流动过程。如果是在qq上发送文件呢?
为什么要有内存呢?提高效率,降低使用成本
二 操作系统(Operator System)
2.1 概念
任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:
● 内核(进程管理,内存管理,文件管理,驱动管理)
● 其他程序(例如函数库,shell程序等等)
在后面的描述中,我们认为,操作系统(OS)就单指内核
操作系统基本都是是由C语言构成的,少量汇编语句
OS的本质其实是一种,对软硬件进行管理的软件
2.2 设计OS的目的
2.3 OS如何进行管理
对OS如何进行管理有很多层面的理解,这里我们介绍顶层理解,也方便我们在之后的学习中更容易理解
举个简单的例子
在学校里,校长是我们公认的管理者,我们学生是公认的被管理者,可是我们大多数人却没有见过校长,但是校长依旧可以把我们所有人都管理得很好 ,可见,管理者与被管理者不需要直接接触
但是校长如何管理我们呢,辅导员这时候便登场了, 辅导员接受校长的决策,并对学生执行决策工作,校长通过辅导员了解学生,但校长不会认识每一个学生,那校长如何对学生进行管理呢?我们知道,每个人是不同的,但每个人都有共同的属性(姓名,年龄等等)
校长通过掌握学生的数据,便可以管理学生,我们可得,管理的本质,其实是对数据的管理
如果人一多了怎么办呢
总结:
操作系统也同样如此
整体:
2.4 总结
计算机管理硬件
描述起来,用struct结构体
组织起来,用链表或其他高效的数据结构
系统调用和库函数概念
● 在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。
● 系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统 调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发。
承上启下
那在还没有学习进程之前,就问大家,操作系统是怎么管理进行进程管理的呢?很简单,先把进程描述起来,再把进程组织起来!
三 进程的标示符
3.1 基本概念
课本概念:程序的一个执行实例,正在执行的程序等
内核观点:担当分配系统资源(CPU时间,内存)的实体。
从我们现存的知识来说,内核观点我们无法理解,课本概念又流于表面,不知原因
一图解析:
ps:动态调度则是在进程运行过程中根据实际情况动态地调整进程的优先级。
描述进程-PCB
● 进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
● 课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struc
task_struct-PCB的一种
● 在Linux中描述进程的结构体叫做task_struct。
● task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。
task_struct 是内存级别的操作,当我们启动一个进程时,在操作系统内部会自动创建一个Task_struct,不会进行磁盘操作
task_ struct内容分类
● 标示符: 描述本进程的唯一标示符,用来区别其他进程。
● 状态: 任务状态,退出代码,退出信号等。
● 优先级: 相对于其他进程的优先级。
● 程序计数器: 程序中即将被执行的下一条指令的地址。
● 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
● 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
● I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
● 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。 其他信息
3.2 查看进程
3.2.1 ps ajx指令查看所有进程
我们写一段代码并把它运行起来
cpp
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
while(1)
{
printf("welcome to my blog\n");
sleep(1);
}
return 0;
}
j接着在同一个终端输入如下命令
ps axj | head -1 && ps axj | grep myproc | grep -v grep
我们给这个可执行程序写的是死循环的,所以会一直运行下去,但是我们可以用kill指令利用标示符强制杀死这个进程 或者直接CTRL+C
kill -9 进程标示符
3.2.2 /proc
Linux为了方便我们查找进程相关属性,把进程的属性以文件的形式显示,操作系统启动时,会自动扫描PCB,并通过proc以文件形式展现,本质是内存级别的文件,他是对动态运行的所有进程的一个可视化信息!!
打开我们有着我们进程的PID的文件
1、其中exe说明当前进程是由哪一个路径的可执行文件加载来的
2、cwd表示的是当前进程的工作目录(current work dir) ,所以为什么你fopen出来的新文件会被默认放在当前目录,这其实是由该进程的cwd决定的!!(在代码写chdir改变当前工作目录)
3.3 通过系统调用获取进程标示符
进程id(PID)
父进程id(PPID)
3.3.1 系统调用接口getpid 和getppid
3.2.2 理解ppid
什么是PPID呢??我们来看看刚刚执行程序的PPID是什么进程
我们都知道其实bash是命令行解释器,命令行的作用一方面是解释命令,另一方面是为了阻止用户的非法操作,而我们每一条指令或者是可执行程序其实都是一个进程,因此我们的bash命令行其实是创建了一个子进程去执行对应的指令,然后自己就可以继续去帮助别的指令创建进程,这样的好处就是一旦子进程崩了,并不会影响bash命令行进程处理其他的指令,与我们Linxu第一节Linux的组成呼应 Linux:Linux权限理解-CSDN博客
我们会发现就是当我们重新运行程序的时候,只会改变子进程的id,父进程id并不会改变,而当我们把机器关了重新开了的似乎,父进程id却改变了,这说明父进程(bash命令行)是在打开机器的时候就创建好的一个进程
3.4 通过系统调用创建进程-fork(重要)
更为重要的我们要知道fork的返回值
如果成功了,会返回一个子进程的pid给父进程,然后还会返回一个0给子进程,如果失败,返回-1给父进程,没有子进程被创建!! ------>这说明fork有两个返回值
为了方便观察后面的结果,我们先写一段代码
cpp
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <unistd.h>
4 int gval = 0;
5 int main()
6 {
7 while(1)
8 {
11 printf("gval: %d\n", gval);
12 printf("I am a process, pid: %d, ppid: %d\n", getpid(), getppid());
13 pid_t id = fork();
14 if(id > 0)
15 {
16 while(1)
17 {
18 printf("我是父进程, pid: %d, ppid:%d, ret id: %d, gval: %d\n", getpid(), getppid(), id, gval);
19 gval+=10;
20 sleep(1);
21 }
22 }
23 else if(id == 0)
24 {
25 while(1)
26 {
27 printf("我是子进程, pid: %d, ppid:%d, ret id: %d, gval: %d\n", getpid(), getppid(), id, gval);
28 gval++;
29 sleep(1);
30 }
31 }
32 sleep(1);
33 }
34 return 0;
35 }
运行之后我们会发现:if和else if同时在进行
问题一:fork为什么要给子进程返回0,给父进程返回子进程pid?
因为一个父亲可以有多个子女,但是一个子女只能有一个父亲(即父:子 = 1:n) 。所以对于父进程来说他未来可能需要去控制子进程,所以就需要对应子进程的PID (用来标定子进程的唯一性 )。而子进程只需要用getppid就知道其父进程了,所以我们返回个0就可以了
问题二:fork函数如何起到作用+如何有两个返回值
因为fork函数会创建一个子进程,而进程=内核数据结构(task_struct)+程序的代码和数据,
父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)
父子的代码是共享的,所以return id也是属于代码,父子在被调度运行的时候,各自会运行一次return,因此父子进程各返回了一次!
问题三 一个变量为什么会有不同的内容?
暂定
四 进程的状态
4.1 进程的状态
在不同的操作系统下,进程的具体状态时有差别的,但普遍遵循如下规则
一图解析:
运行时挂起,风险大,一般不用
挂起一般是在内存严重不足的时候,OS会将未处在调度队列的进程的代码和数据存入磁盘的swap分区(进行IO操作,速度慢),等到进程要使用时,再从swap分区中换入
4.2 Linux中的进程状态
看看Linux内核源代码怎么说
为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在 Linux内核里,进程有时候也叫做任务)。
下面的状态在kernel源代码里定义:
cpp
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
● R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列 里。
● S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠 (interruptible sleep))。
● D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的 进程通常会等待IO的结束。
● T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可 以通过发送 SIGCONT 信号让进程继续运行。此时程序转到后台运行,只能用kill停止
● X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
我们写一个死循环代码并让他运行起来
cpp
#include<stdio.h>
int main()
{
while(1)
{
printf("hello\n");
}
return 0;
}
为什么进程显示S+,因为对于一个进程的时间片是很短的 printf并不是直接往显示器上打的,它是往内存里写的,相当于显示器也有自己的缓存,显示器缓存很容易被写满,你的显示器并不是时时刻刻就绪的,IO操作很耗费时间,在CPU调度的时候,外设不一定就绪,大部分处于S状态,可能偶尔出现R状态
健康的操作系统,一般不会出现D状态,因为D状态是瞬时的,很难被查到 ,D状态------系统中有高IO(几百G)或者磁盘响应过慢(老化,空间不足) 长时间等待IO过程,若一直处于D,则可能IO,磁盘出现问题,操作系统离挂掉不远了
在可执行文件后面加一个&,可使程序后台运行
Z(zombie)-僵尸进程
僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲) 没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
echo ¥? 显示最近一个程序执行时的退出信息
孤儿进程
父进程先退出,子进程就称之为"孤儿进程"
孤儿进程被1号init进程领养,当然要有init进程回收喽
cpp
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if(id < 0){
perror("fork");
return 1;
}
else if(id == 0){//child
printf("I am child, pid : %d\n", getpid());
sleep(10);
}else{//parent
printf("I am parent, pid: %d\n", getpid());
sleep(3);
exit(0);
}
return 0;
}
五 进程优先级
5.1 基本概念
● cpu资源分配的先后顺序,就是指进程的优先权(priority)。
● 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能
● 还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。
查看系统进程
在linux或者unix系统中,用ps --l命令则会类似输出以下几个内容:
5.2 PRI and NI
● PRI也还是比较好理解的,即进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小 进程的优先级别越高
● 那NI呢?就是我们所要说的nice值了,其表示进程可被执行的优先级的修正数值
● PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice
● 这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行 所以,调整进程优先级,在Linux下,就是调整进程nice值
● nice其取值范围是-20至19,一共40个级别。
可以理解nice值是进程优先级的修正修正数据
5.3 用top命令更改已存在进程的nice
● top
● 进入top后按"r"-->输入进程PID-->输入nice值
为什么进程优先级的范围 只有【60,99】呢,这要等我我们深入学习进程调度算法再解释
5.4 其他概念
● 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高 效完成任务,更合理竞争相关资源,便具有了优先级
● 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
● 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行
● 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为 并发
六 进程的调度切换
我们知道Linxu是分时操作系统,根据进程的时间片对进程进行调度
上图为早期的Linux内核
当代计算机对上下文数据保护设计已经非常复杂了,被放在操作系统的一个段叫TSS,为了方便理解,我们认为他在task_struct
总结
✨✨✨各位读友,本篇分享到内容是否更好的让你理解进程的概念,如果对你有帮助给个👍赞鼓励一下吧!!
🎉🎉🎉世上没有绝望的处境,只有对处境绝望的人。
感谢每一位一起走到这的伙伴,我们可以一起交流进步!!!一起加油吧!!