本文将会对计算机体系结构先进行粗略描述,然后再围绕操作系统的管理进行讲解以及进程相关概念的介绍。这样先对操作系统有个初步的认识和了解,为以后深入学习操作系统做铺垫
谈及计算机体系结构不得不提到经典的冯诺依曼计算机体系结构,现在的计算机主要还是以此作为计算机的体系结构。
输入输出设备都被称为外部设备,以磁盘为例这就是输入设备,数据保存到磁盘上,系统需要用到相关数据时会将其加载到内存中处理,相对于内存来说,磁盘一般是比较慢的。
cpu是速度是最快的,从数据层面一般来说cpu只与内存直接打交道,不与外设直接沟通。外设一般是只于内存打交道。这也是因为由于内存的存在,数据可以提前预加载到内存中等待cpu的处理,cpu根本不需要和内存访问外设。
我们的程序为什么需要加载到内存中?这是由体系结构所决定的。
既然是cpu对数据处理,那么cpu是不是越快越好呢?其实有个名词叫木桶原理,一只木桶盛水的多少,并不取决于桶壁上最高的那块木块,而恰恰取决于桶壁上最短的那块。磁盘这种外设的速度相对于cpu是很慢的,所以这个时候计算机的整体效率取决于外设。
对冯诺依曼体系的理解,不能停留在概念上,要深入到对软件数据流理解上。下面我将以QQ发送消息为例,简单粗略的从体系结构角度分析这个过程。
之所以先了解计算机体系结构,这是因为操作系统都围绕着这个大框架来运行操作的,基于硬件结构决定处理方式。
操作系统是一款管理系统软硬件资源的软件。那么操作系统怎么管理呢?我对管理进行建模举例,来帮助理解管理的本质。
对上述校长管理学生抽象建模的例子中,我们可以得到重要结论:
管理的本质是对被管理者信息先描述 ,再组织。
回到操作系统中,其实也是这样,操作系统对某事物的管理,就是先将其描述成某种信息结构,再将这些结构组织成某种数据结构进行操作处理从而实现了管理。
以刚才的学生管理为例,管理好学生才能维持一个学校正常稳定的运转。同样的,
操作系统对下管理好软硬件资源(手段),对上给用户提供良好稳定高效的服务。
对下的管理
操作系统怎么对上服务呢?
下再举个例子
操作系统提供了一系列的系统调用接口,通过这些接口服务于用户,同时也保证操作系统本身的安全稳定。但是由于这些接口相对来说操作比较复杂,因此开发者基于这些接口研发出更利于普通用户操作的应用软件。
在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发。
网上给出的定义是系统中正在运行的一个程序。
我们启动并运行程序的行为由操作系统帮助我们将其转化成进程,用来完成特定的任务。例如我们在电脑上启动运行QQ,操作系统就会将其转成进程处理。
下面我将举例理解
由此引入了pcb的概念,进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。在Linux中pcb被描述成结构体叫做task_struct。同时进程的准确描述应该是
关于进程的数据结构+进程中的代码和数据
。同时,之所以创建pcb块也是为了将进程进行有效的管理。
我们之前讲过文件=内容+属性,进程中的代码和数据是属于文件内容的,但是pcb中进程的属性和文件的属性是一样的吗?并不是,pcb中的进程属性的基于进程产生的,显然是另一种东西。
新建一个c程序为死循环打印hello,使用ps指令查看进程信息.
我新建以后同时写了makefile文件,可执行程序被命名成myfile,下面我将以myfile为例输入指令操作。
ps ajx
# 查看当前所有进程信息
我们通过管道来过滤一下这些信息
ps axj |grep myfile
#将由程序myfile生成的进程信息以文本的形式过滤出来
ps axj |head -1 |grep && ps axj |grep myfile
# 将将由程序myfile生成的进程信息的第一行以文本的形式过滤出来
这个&&表示的意思,前面的指令执行成功后就在此基础上执行后面的指令。同时,我们发现图中还有grep,这是因为grep作为指令被执行后也是作为进程,我们也可以将其过滤掉。
ps axj |head -1 && ps axj |grep -v grep |grep myfile
我们可以进入根目录下的proc,该目录是保存进程相关属性的目录。 它是内存级的目录,不存在于磁盘上,只有操作系统启动时才会存在。
当我们将进程结束后,以进程pid命名的文件夹就会消失
这里我按下ctrl C终止程序,进程结束了,再输入指令就会发现命令行提示该目录不存在了
我们可以看到这里的两个信息pid 和ppid,pid就相当于进程的编号,类似于学生的学号,ppid是当前进程的父进程的pid,这些都是属于进程的属性被保存在pcb结构体中
Linux中提供了一个系统调用接口getpid用来获取当前进程的pid,我们可以通过man手册进行查询。
这里看到getpid的返回值是pid_t类型,这是系统特有的一种类型本质是无符号整型,这个函数需要包含图中的头文件。
我们除了能获得进程的pid还能获得当前进程的父进程的pid,也就是当前进程的ppid,系统也给我们提供了一个系统调用接口getppid
这个进程是bash,关于bash之前我们就讲过bash是shell外壳程序,命令行解释器。它本质上也是一个进程,命令行启动的所有程序,最后都会变成进程,该进程对应的父亲进程就是bash。之前以媒婆举例时,王婆为了不影响自己的口碑就招聘了实习生。当时说的是这就对应着创建子进程,这里我们就看到了bash创建的子进程。如果由bash直接处理运行程序,原来的代码程序有问题就可能会让bash挂掉,所以创建了子进程来处理。这样如果程序有问题也不会影响到bash。
刚才终止程序都是用的ctrl+c,现在介绍一条指令用来终止进程
kill -9 进程pid
到此我们知道了程序的一次运行是系统将程序转变成进程使之运行起来
在创建子进程之前先介绍个vim多行注释的快捷方法,进入vim后在命令模式下按下ctrl v进入视图模式,之后按下j,就是光标向下移动,到达你想要停留的行处按下大写的i之后输入注释符号//,按下esc即可。
ctrl +v j(选定重终点行号) I 注释 esc
去掉多行注释除了按u回撤外,还可以进入视图模式,按hjkl方式移动光标选中区域,按下d即可
Liunx中也提供了一个接口fork函数用来创建子进程
接下来我们来看看fork函数的返回值
fork函数的返回值:如果进程创建成功,子进程的pid会返回给父进程,0返回给子进程,如果创建失败了,返回-1.
这句话看起来好像有点莫名其妙,我们来做个实验看看。
我们发现r第二行的ret返回了6634,6634就是子进程的pid,同时我们发现第3行的ret返回了0,这也就对应了进程创建成功,子进程的pid返回给父进程,0返回给子进程。但是我们不禁有个大大的疑问,为啥一个函数能有两个返回值呢?同时为啥同一个地址会写入不同的值的呢?
关于同一个地址为啥会被写入两个值显然这个地址肯定不是物理地址,这个地址其实是虚拟地址,关于这个地址问题后续会有介绍。两个返回值的问题,让我们接着来做实验。
这里if 和if else同时执行了也是很奇怪的现象,暂且我们先放一放,等下将会解释。我们来总结以下fork的用法。
如何创建子进程呢?
使用fork函数。fork之后执行流会变成两个执行流,通常使用if else 语句来进行分流操作。
关于fork之后父子进程谁先被运行取决于系统调度器,fork之后父子进程代码的共享的,这一点可以由之前打印的时候,print打印出两行b可以看出。
这里就是发生了写时拷贝
,尽管看起来x的地址都是一样的但是打印出来的值其实是不同的,这就像之前的看到的ret值不同。所以x的地址也不是物理地址,也是虚拟地址。
其实我们平时写代码的时候,如果函数将要执行返回语句那就说明retrun之前的功能语句都已经执行完成了,return只是告诉执行结果。
所以当fork执行到return时,父子进程已经创建好了,一个进程由一个变成了两个,两个进程是共享代码的,return的时候两个进程都会执行 相当于两个进程分别执行了一次,所以看起来像是返回了两次。
为什么会else if,那是因为两个进程各自有自己的进程空间,表面看到的是一份代码其实底层父子进程各有一份代码,因为代码共享所以完全相同,各自执行各自的代码,此时就根据不同的返回值选择不同的分支进行执行了
所以看起来像是同时进入条件判断语句,其实各自进程分别进入。
关于进程空间和虚拟地址这块后续将会一一介绍,这里就不过多赘述了
以上内容如有问题,欢迎指正,谢谢!