一、冯诺依曼体系结构
学过计组的同学应该都很熟悉这个结构,可以说这是计算机的基础了:
其实我们日常就经常使用到该结构中的各个部分:
输入单元:包括键盘, 鼠标,扫描仪等。
输出单元:显示器,打印机等。
还有中央处理器(CPU):由运算器和控制器组成。
存储器:即计算机的内存。计算机的外设(输入输出设备)和CPU的互相访问都要通过内存。
二、操作系统
2.1操作系统的概念
什么是操作系统,我们日常使用的手机操作系统有IOS、安卓(AOSP),以及最近的鸿蒙OS和Xiaomi HyperOS,光是苹果的操作系统就有Mac OS、IOS、iPad OS...我们发现他们都有一个特点,都要加OS,那么什么是OS?
OS即 Operator System 的简称,笼统地说,操作系统包括:
1.操作系统内核:进行进程管理、内存管理、文件管理等。
2.其他程序:如函数库、Shell程序
2.2设计操作系统目的
我们根据下图和学过的知识,用户可以通过 Shell 等和操作系统交互,操作系统底层的驱动程序和硬件均由各个厂商提供,操作系统又可以通过驱动程序和计算机的硬件直接交互。
操作系统拥有着上传下达的使命,对下可以管理所有的软硬件,对上可以为用户提供良好的(高效、稳定、安全)运行环境。
图片来源:比特科技
三、进程的概念
3.1基本概念
从内核来说:进程是担当分配系统资源(CPU时间,内存)的实体。
一个程序未被执行时存在于磁盘中,而磁盘属于外设的一种,如果我们想调用该可执行程序,则需要把它放在内存中(冯诺依曼体系结构:计算机的外设(输入输出设备)和CPU的互相访问都要通过内存。):
那么进程到底是什么?
进程对应的代码和数据这么多,CPU是如何分辨执行顺序的优先级或其他的?
进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。课本上称其为 PCB (内核数据结构,即操作系统的数据结构 ) ,Linux 下称其为 struct task_struct :
我们的操作系统也是软件,在未开机时它也是一堆二进制文件,当我们开机时,操作系统其实是第一个加载到内存的软件,所以上面的内核数据结构都存在于操作系统占用的内存中,开机时系统根据进程 malloc 出 PBC 的空间,当我们开机时等待的时间就是计算机把磁盘中的软件拷贝到内存中的时间。
所以 进程 = 内核task_struct结构体 + 程序的代码和数据
task_struct内容分类:
标示符: 描述本进程的唯一标示符,用来区别其他进程。
状态: 任务状态,退出代码,退出信号等。
优先级: 相对于其他进程的优先级。
程序计数器: 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
I/ O状态信息: 包括显示的I/O请求,分配给进程的I/ O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
其他信息
3.2查看进程
[ps axj] 命令可以查看当前所有进程,axj顺序可颠倒
[ps axj | grep Filename] 命令可以筛选并查看名为Filename的进程:
[ps axj | head -1] 命令可以查看以上进程每列表示的信息:
将以上两种命令通过 [&&] 结合即可同时生成:
这些信息的含义我们在下面再为大家介绍。
3.3启动进程
a.Linux 下我们通过 [./XXX] 命令来运行某个文件,本质上就是让系统创建进程并运行,而我们自己写的各种程序其实和系统中的命令是等价的,他们都是可执行文件,他们瞬间把我们的需求加载到内存,在 Linux 下大部分执行操作,本质上都是在运行进程
b.通过 [ps axj | grep Filename] 与 [ps axj | head -1] 命令,我们查看到进程信息有[PID],我们的每个进程都有独特的标识符——进程[pid]
c. [Ctrl+c] 指在用户层面终止进程,[kill -9 [pid]]可以直接杀掉进程。
3.4系统调用 && get pid
3.4.1什么是系统调用
在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用,这也就是面向对象语言的封装性。
我们在C++中使用的 cin、 cout ... 等都是系统调用,它们的库是 std 标准库,我们的进程想知道自己的pid,应该怎么做呢?这就用到了系统调用的知识。
3.4.2 get pid
系统为我们提供了一个接口函数 [getpid()],便于我们查看进程的 pid 。
我们还可以用 [man getpid] 命令来查看 getpid 函数:
如果你无法执行 [man getpid] 这个命令,而且系统报错:[No manual entry for getpid] ,请尝试先执行以下代码:[sudo yum -y install man-pages],安装完成后执行 [sudo yum update] 。
3.5 ppid
3.5.1 get ppid
首先我们来看一下进程内部属性里的 [ppid] ,它是指当前进程的父进程的 id ,我们用 [get ppid()] 这个函数一样可以查到它是什么。这里先简单认识,下面我们来看一个细节:
这是为什么呢?我相信每次的 pid 都不相同大家都能理解,因为执行的时间不一样,每次随机分配的 pid 也不一样是正常的,那为什么 ppid 总是一样的呢?
3.5.2 ppid
接下来我们来看看 ppid 是什么呢?
它竟然是 bash !我们在哪里还遇到过 bash 呢?
我们在 Linux 权限时讲过,我们在使用操作系统时,通过外壳程序(即XShell)来先完成,而外壳程序为了保证自身的稳定性,会创建一个子进程让它来执行我们的命令,这个子进程名为 bash !
3.6进程创建的代码方式
我们先来看一下新的命令,该命令可以持续地帮助我们查看进程的信息:
while : ; do ps axj | head -1 && ps axj | grep Filename | grep -v grep; sleep 1; done
其中褐色部分是 Shell 命令,意思是重复执行内部的代码;蓝色部分是我们需要执行的代码,也就是前面讲的查看进程信息的代码, [grep -v grep] 意为筛出掉包含 grep 的信息,也就是说我们查看进程信息时,不会再看到 grep 进程的信息行。
那么如何创建进程呢?这里我们要接触到第三个系统调用, fork() 函数。
我们同样可以用 [man] 命令来查一下 fork() 函数具体的用法:
下面我们编写一下简单的代码来看一下 fork 的作用:
我们持续地查看进程的信息,从中得到了子进程和父进程。
3.7详解fork()
我们看上面的例子,父子进程执行同一份程序,如果我们想执行同一份程序,其实没必要用到子进程,在一些特殊的情况,我们需要父子进程执行不一样的程序,这才是我们子进程的目的。
我们先来了解一下fork()的返回值:
fork() 会返回两次,其有两个返回值。
当执行的是子进程时,返回值是0;当执行的是父进程时,返回值为子进程的 pid 。
我们来看下面的程序,我们发现 if 和 else 中的程序全都被执行了,这是为什么呢?