- 硬件系统
- 软件系统
- 进程概念
- 进程状态
- 孤儿进程
- 进程优先级
一.硬件系统
1.1 冯诺依曼体系结构
数学家冯·诺依曼提出了计算机制造的三个基本原则,即采用二进制逻辑、程序存储执行以及计算机由五个部分组成(运算器、控制器、存储器、输入设备、输出设备),这套理论被称为冯·诺依曼体系结构。
1.2 外部设备
输入设备和输出设备统称为外部设备,也叫做外设。这类设备的特点就是访问速度比较慢。
- 常见的输入设备:麦克风,网卡,磁盘,键盘,鼠标…
- 常见的输出设备:显示器,网卡,磁盘…
1.3 中央处理器
中央处理器(Central Processing Unit,简称CPU)作为计算机系统的运算和控制核心,是信息处理、程序运行的最终执行单元,它由运算器和控制器组成。它的存取数据速度非常快。
1.4 存储器-内存
计算机的本质是计算,那么一定需要输入设备输入数据,cpu进行对数据的计算和加密…,输出设备输出运算结果。那么存储器有什么用呢?
如果计算机硬件结构只有外设和cpu,一定是可以的,但是由于外设的访问速度和cpu的存取速度差距太大, 最后计算机的速度就取决于外设的速度了,参考木桶原理。存储器的引入就是为了提高计算机的运行速度。
- 内存可以预加载一些程序/数据,这也可以提高计算机的运行速度-局部性原理。
- 在数据层面上,cpu只和内存进行沟通,外设也只和内存进行沟通
由于体系结构的规定,我们所编写的程序,一定要先加载到内存中,才会执行。
- 程序就是二进制文件,文件存储在磁盘上,而磁盘只能与内存进行沟通。
一台计算机只有硬件系统是无法正常运行的。比如:内存满了怎么办,预加载哪些程序和数据,输出的数据需要写到磁盘的哪个位置,这些问题是硬件系统无法解决的。此时就需要一个软件来管理这些硬件,解决这些问题。这个软件就是操作系统。
1.5 数据的流向
数据在一台计算机中的流向,一定是从输入设备到内存,内存到cpu,cpu运算结束后返回到内存,然后从内存到输出设备。
二.软件系统
2.1 操作系统的概念
操作系统_(Operating System,缩写:OS)_是一组主管并控制计算机操作、运用和运行硬件、软件资源和提供公共服务来组织用户交互的相互关联的系统软件程序。根据运行的环境,操作系统可以分为桌面操作系统,手机操作系统,服务器操作系统,嵌入式操作系统等。 、
- 操作系统:管理软硬件资源,为上层用户提供安全,稳定,高效的运行环境。
2.2 管理的本质
操作系统管理的本质:先描述,再组织。描述就是面向对象的过程,组织就是数据结构。
例子:
在学校的管理体系中,管理者并不需要见到学生才能管理他,管理者只需要知道学生的数据就可以很好的管理该名学生。但是仅仅知道该名学生的数据还不够,如果只有1名学生,这样的管理明显是可以的,但是如果有3万名学生呢,就算知道了这些学生的数据也不好管理,此时我们可以将这些学生的信息抽象成一个结构体,然后对于每一名学生构建一个结构体对象,然后使用链表,树等其他数据结构,组织这些结构体对象。这样,对于3万名学生的管理就变成了对数据结构的增删查改。同理,操作系统也是这样管理软硬件的,先将这些软硬件的属性信息抽象为一个结构体,然后构建数据结构,对数据结构进行管理。
- 这些硬件的属性信息是由硬件驱动来提供给操作系统的
2.3 计算机软硬件体系结构
操作系统虽然给用户提供服务,但是它不相信任何人,因为有些用户不了解硬件,如果让他随意访问,很有可能造成不可逆转的影响。比如:银行体系结构,它为人们提供服务,但是它不相信任何人,所以提供了一个一个小窗口来供人们使用。这里的小窗口在计算机中就是操作系统为用户提供的系统调用接口,用户可以通过系统调用接口来让操作系统帮他完成指定任务,但是由于系统调用接口的使用成本比较高,因此有人就基于这些系统调用接口开发了图形化界面,shell以及工具集和一些库。用户使用这些程序来执行任务的成本明显低于直接使用系统调用接口。
- 系统调用(system call):本质上是c函数,只是在操作系统内部的c函数
- 如果一个app使用了当前操作系统的系统调用接口,则该app只能在当前操作系统上运行,不具备跨平台性
- 比如touch命令,一定调用了系统调用接口,然后操作系统发起了创建文件的命令,驱动程序接收到命令后,交给硬件执行命令,然后自低向上返回。象这样命令的执行,一定是贯穿体系结构的。
- 库的底层实现不一定调用了系统调用
三.进程概念
一个可执行程序是如何运行的呢?在Windows中,是通过点击程序;在Linux中,是通过./
。这种行为,其本质上是操作系统将该程序加载到内存中,将该程序转换为进程,来完成特定的任务。
操作系统如何管理这些进程呢?管理的本质是先描述,再组织,即先获取这些进程的属性信息,将它们填充到结构体对象中,然后用链表链接起来,这些结构体对象在操作系统课本中叫做PCB,在Linux中叫做task_struct。操作系统对进程的管理,就转换为对这些PCB的管理。也就是说进程 = 进程在内核中的数据结构 + 进程相关的代码和数据。
3.1 task_struct
PCB(process control block),LInux下的PCB是task_struct。task_struct是Linux内核的一种数据结构,它会被装载进内存中。
- 标示符: 描述本进程的唯一标示符,用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器: 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
- I/ O状态信息: 包括显示的I/O请求,分配给进程的I/ O设备和被进程使用的文件列表。
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
- 其他信息
- 多进程中,PCB一定是多份的,但代码和数据不一定有多份
3.2 查看进程的id的系统调用
在系统调用中,getpid和getppid可以查看当前进程的pid和ppid(父进程的pid)。
3.3 查看进程信息的命令
- ls /proc
proc目录并不在磁盘中,它是一个内存级别的目录。创建进程时,操作系统会自动在该目录下创建一个以该进程的pid为名的目录,进程退出时,自动销毁。
- ps 命令
- ps axj | head -1 && ps axj | grep 进程名 | grep -v grep
- top 命令- 任务管理器
通过上述观察,我们发现在命令行启动的所有程序都会变为进程,其父进程大都是bash(shell的一种),且bash也是一个进程。
bash,是命令行解释器,本质上是一个程序。操作系统将bash程序加载进内存,创建属于它的pcb,将该程序变为进程,完成命令行解释的功能。由于bash要保证自身的安全性,所以它在执行命令行解释时,常常是创建子进程来执行,因为进程之间具有独立性,所以子进程出事,bash也不会出事。
3.4 创建子进程的系统调用
fork是一个系统调用,它主要用来创建子进程。
- fork之后,执行流变为两个
- fork之后,fork后的代码两个执行流共享
- fork之后,通常是根据if else 语句来进行分流
- fork给子进程返回0
- fork给父进程返回子进程pid
- fork的功能是创建子进程,本质上是创建PCB,代码和数据与父进程共享
- fork之后,如何保证父子进程之间的独立性呢?
代码:只读,两个进程之间不会相互影响。
数据:当一个进程要修改数据时,操作系统会对该数据触发写时拷贝机制。从而保证数据独立性
- fork之后,由于当fork函数执行到return语句的时候,已经完成创建子进程的功能,也就是说已经有两个执行流了,所以fork有两个返回值也不奇怪。
3.5 杀死进程的命令
- kill
kill -l 可以查看传递哪些信号
- kill -9 pid 杀pid的进程
- killall 进程名 杀指定名字的进程
- ctrl + c
四.进程状态
为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在
Linux内核里,进程有时候也叫做任务)。
/*
* 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 */
};
为了理解上述进程状态,需要先了解两个概念,阻塞和挂起。
4.1 阻塞
一个进程由于要等待某种条件的就绪,从而导致的一种不推进状态。阻塞是不被操作系统调度,一定是因为等待某种资源,进程对应的task_strcut在某种被操作系统管理的硬件资源队列中排队。
- 进程可以在不同的资源队列中排队,进程是什么状态,一般也看在哪排队
比如:
scanf函数,当程序执行到scanf函数时,该进程就会跑到键盘的等待队列中排队。等待资源就绪的这种进程状态,就是阻塞。
4.2 挂起
当一个进程状态是阻塞时,操作系统在内存不足的情况下,会将该进程对应的代码和数据,暂时交换到磁盘中,在磁盘中这块空间也叫做swap分区(外存),等资源就绪后,再将代码和数据交换回来。如果一个进程的代码和数据被交换到磁盘中,该进程的状态就是挂起。
4.3 R -运行状态
在操作系统内核中,维护着一块进程运行队列,当一个进程在该队列中排队时,就会处于R状态。
- 处于R状态的进程,不一定正在运行,也有可能在运行队列中排队
4.4 S -可中断休眠,本质是阻塞状态
比如当一个程序中有scanf函数时,运行到该函数处,进程就会到键盘的资源队列下排队,这种状态就是S,此时可以杀掉该进程(kill -9 or ctrl + c),所以也叫做可中断休眠。
4.5 D -不可中断休眠
当有高IO的情况下,该进程的状态就是D,不可被操作系统杀掉,防止写入失败时,导致出现数据丢失问题。
4.6 T 暂停
当一个进程运行时,出现了不当操作,但是操作系统又不想杀进程时,就会将该进程设置为T状态,我们也可以手动设置该状态。
- kill -19 pid 将pid进程设置为停止状态
- kill -18 pid 继续运行一个停止状态的进程
- 进程状态s+
- +表示前台运行,可以通过ctrl c 和 kill -9 杀掉进程
- 没有+ 表示后台运行,只能通过kill -9 杀掉进程
4.7 t 暂停
当程序运行至断点处,gdb就会向操作系统发送一个信号,将该进程的状态设置为t
4.8 X状态
当一个进程退出时,会立即变为X状态,立即退出,所以无法观察到
4.9 Z状态
我们创建进程的目的是为了帮我们完成任务,那么我们就要关心任务完成的怎么样,任务完成的怎么样就要通过进程退出码来确定,比如main函数返回0,表示正常退出
echo $? 可以查看进程退出码
当一个进程退出时,就要处于Z状态,一直处于Z状态的进程就叫做僵尸进程。等待父进程/操作系统获取进程退出码。
僵尸进程的危害:
父进程一直不读取,那么子进程就要一直保持Z状态,该进程对应的PCB就要一直维护,导致了内存泄漏。
五.孤儿进程
子进程退出父进程继续运行,父进程不回收,此时子进程就是僵尸进程。父进程退出,子进程被操作系统领养,此时子进程就叫做孤儿进程。
- 由于父进程的父进程是bash,所以父进程退出后,由bash回收,不会形成僵尸进程
六.进程优先级
因为cpu的资源是有限的,所以会导致出现多个进程竞争cpu资源的情况,此时优先级高的进程先被执行,优先级低的进程后执行。
Linux中查看进程优先级:
- ps -l
PRI:priority 即进程优先级,PRI值越低,优先级越高
NI:修正数值,newpri = oldpri + ni[-20, 19]
修改进程优先级:
- top + r + pid + nice值[-20, 19]
Linux中oldpri默认为80,也就是说,newpri = 80 + ni