Linux进程概念(一)
1. 冯诺依曼体系结构
我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系
为什么要有内存呢?
首先,像输入输出设备这些都是外设,这些数据传输都很慢,但是内存数据传输速度很快(可以百度查一查资料),CPU是计算数据最快的。外设虽然慢,但是必须得有,比如键盘,摄像头这些都是必须要用的。不用内存可以吗,也就是直接输入设备传到CPU再到输出设备,这样是可以的,但是效率会下降,因为整体的效率都是以外设为主。如果有了内存就可以提高效率,输入设备输入的数据先预加载到内存中,然后内存再加载到CPU中,这样就不会造成很多数据被阻塞。所有设备(外设)都只能直接和内存打交道,CPU能且只能对内存进行读写,不能直接访问外设(输入或输出设备)
为什么程序必须先被加载到内存中?
体系结构决定的
请解释,从你登录上qq开始和某位朋友聊天,你发了一个在吗?数据的流动过程是怎样的?
首先,在我本地电脑上,我给李四发了条信息,然后数据加载到内存再到CPU对其进行加密等等,然后写入到内存,反馈到我的本地电脑上也就是显示器上同时数据发送到网卡,网卡再把数据刷新到网络中,李四的网卡就在网络中拿到数据,然后加载到内存再加载到CPU,对其数据进行解密等等,然后再写入到内存中,最后到李四的显示器上就会显示一条信息。(硬件决定了数据流动的时候必须遵守冯诺依曼体系结构)
2. 操作系统(Operator System)
2.1 考虑
如果有数据,就可以数据预加载到内存,当我们启动程序时,文件数据很多,那么预加载是那一部分数据?预加载的时候如果内存不够了怎么办?CPU是如何快速找到预加载到内存的数据的?当我们执行多个任务的时候,计算机是先把一个任务搞定再搞另外一个还是同时搞呢?这些都不是硬件能完成的,都是操作系统这个软件来做管理的。操作系统是一款软硬件资源管理的软件,所有的软件要运行数据都是加载到内存中
2.2 如何理解操作系统对硬件做管理?
故事:大学学校中,我们认为有三个角色:校长、辅导员、学生。那么辅导员和校长谁是管理者呢?下面我们先说一说决策和执行概念,在高中的时候,中午我们一般都是还没到饭点,然后就想着中午吃什么,然后心里想吃炸鸡,然后到了中午一下课就跑去买炸鸡吃。这个例子中想吃炸鸡是一个决策,下课跑去买了炸鸡是执行。所以==管理者是大部分工作都是决策,而不是大部分工作是执行,所以校长就是管理者,他所作的大部分工作都是决策,辅导员来执行决策,学生就是来参与决策。故事还在继续,对我们学生来讲,从上大学到现在见过校长没?我估计就是开学典礼的时候见了见。那么为什么没有见过面,怎么把我们学生管的这么好呢?那么必须见面吗?不是的,其实,管理者和被管理者其实是不需要沟通的!那么我们学生和校长没有直接沟通,校长是如何管理我的呢?其实我们的成绩、电话等等数据校长那里都有对其进行管理。所以,生活中管理的本质:不是管理者必须和被管理者进行沟通来做管理,而是对被管理对象的数据做管理!校长和学生没有直接沟通,那么校长作为管理者是怎么拿到学生作为被管理者的数据的呢?辅导员把数据采集给到的校长。但是数据很多,那么校长如何来做管理呢?校长可以通过基本的信息:姓名,性别,年龄、电话,成绩等等这些数据来管理,于是校长自己做了一个系统来管理这些信息,用到的是一个单链表的数据结构来管理这里数据,每一个对象(学生)是struct student{姓名,性别,年龄,电话,成绩…struct student* next};故事还在继续,假如说省里有个数学比赛,那么校长就可以通过对单链表的遍历来找到数学成绩最好的那个学生,这里的决策也就变成了对单链表遍历。然后校长在这个决策后也想到了想看看学校里最差的同学,开除学籍,于是校长的决策就变成了就对单链表做遍历,找到后释放这个对象结点。所以,对学生的所有决策都变成了对单链表的操作。设计单链表也就是对管理做建模。所以,计算机管理的本质:先描述,再组织==。怎么来理解这六个字呢?实际上,上面开除最差的这个学生,先是定义一个单链表的结构,这个定义一个单链表的结构就是先描述,然后再对结点进行链接就是再组织。那么操作系统对硬件做管理也就是先描述再组织。那么,这里的学生就相当于是硬件或者软件,这里的辅导员扮演的角色是硬件驱动,校长就相当于操作系统。
2.3 操作系统为什么要对软硬件资源做管理呢?
计算机是给人提供更好服务的,那么操作系统通过对下管理好软硬件资源(手段),对上给用户提供良好(安全,稳定,高效等等)的执行环境(目的)。
2.4 系统调用和库函数概念
另外一个故事:一个银行,对应的有电脑、服务器、桌椅板凳、仓库、员工宿舍等等硬件,也有管理电脑的IT部门,也有管理桌椅板凳的后勤部门,也有仓库管理员,也有宿舍管理员,最高的职位是行长,这里的行长可以通过管理这几个部门来管理硬件,银行也有接待工作人员,存款工作人员等等,这些行长也可以直接进行管理。那么,人可以管理硬件也可以管理人,那么操作系统也可以管理硬件也可以管理软件。这里有个问题:银行相信你吗?银行可以把仓库的钥匙给你,然后你存钱直接放在仓库里,仓库里有很多钞票,这是可能的吗?不可能,所以,银行是不会相信你的。所以,操作系统给我们提供非常良好的服务并不代表操作系统相信我,操作系统不相信任何用户。现实生活中,银行既要为我们提供服务又不让我们访问任何细节,那么现实生活中,银行是怎么既保证服务,又保证不对其内部内容做修改呢或者保证自身的安全呢?银行有柜台来既保证服务又保证不对其内部内容做修改呢或者保证自身的安全。==操作系统为了保证服务和自身安全,是以接口的形式来提供服务的,在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。这里的接口就相当于银行的柜台。Linux操作系统是用c语言写的,这里的系统调用其实就是操作系统设计的C语言函数==。系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发,这样就有了对应的图形化界面的产生以及shell和工具集。C语言中printf其实就是对硬件读写,用户不是直接绕过操作系统来完成的,必须通过系统调用接口来访问硬件,所以就有人设计出了C语言,C语言中也有很多接口来实现功能,比如stdio.h中就有很多IO接口。库函数和系统调用的关系:库函数可能调用系统调用,但不是所有库函数。
2.5 计算机体系结构
3. 进程的初步理解
3.1 进程概念
先来看一下windows下的进程:打开任务管理器,就可以看到进程这个字眼,通俗来讲进程也就是任务。下面有很多任务,比如typora、wechat、xshell等等,现象:这里是可以多个任务同时进行的;当结束其中一个任务并不会影响其他任务的运行(进程具有独立性)。
总结:任何启动或运行程序的行为最终都由操作系统帮助我们来将程序转换称为进程完成特定的任务。
操作系统对加载到内存的程序做管理,怎么理解?
在Linux下我们的可执行程序就是一个文件,首先这个可执行程序是放在磁盘上的,然后运行时就被加载到内存中,加载的内容是==代码和数据==,这样就被称为进程吗?并不是,只是完成了加载数据和代码,这里没有说明操作系统对其进行管理。当磁盘中加载到内存的程序有很多个,操作系统必须对其做管理,不管理就乱完了,所以操作系统必须管理进程。
操作系统又如何管理进程呢?
先描述再组织。先描述就是对其每个进程创建一个数据结构对象,这个数据结构课本上称为PCB(process control block),Linux操作系统下的PCB是task_struct,这个结构中有进程的所有相关属性,具体的属性后期再介绍。
task_struct内容分类
标示符: 描述本进程的唯一标示符,用来区别其他进程
状态: 任务状态,退出代码,退出信号等
优先级: 相对于其他进程的优先级
程序计数器: 程序中即将被执行的下一条指令的地址
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器
I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等
其他信息
什么是进程?
进程 = 内核关于进程的相关数据结构 + 当前进程的代码和数据
为什么进程管理中需要PCB
操作系统要管理进程,所以需要
3.2 与进程见面
ps(全称:process status):命令用于显示当前进程的状态,类似于 windows 的任务管理器
ps axj | head -1 && ps axj | grep proc | grep -v grep:查看当前程序进程信息
进程信息也可以通过/proc系统文件夹查看
运行了两个可执行程序后发现有两个进程,见面进程。(PPID:parent process ID;PID:process ID)
ctrl + c :终止进程
通过系统调用获取进程标示符
getpid():获取进程标识符
getppid():获取父进程标识符
getpid用法(getppid用法一样)
现象
每次启动程序,进程PID值可能都不一样。因为每次启动,操作系统都会重新创建PID。
父子进程
当一个可执行文件多次执行时,我们发现PID是变化的,因为是操作系统每次都要重新创建,这个可以理解,但是为什么父进程永远没有变化呢?
PPID是bash,所以bash命令行解析器本质上也是一个进程,命令行启动的所有程序最终都会变成进程,该进程对应的父进程是bash。
bash前面也介绍了,他就相当于一个媒婆,但是为了不给自己的招牌砸了叫人来替代它来办事。所以它是通过创建子进程的方式来执行代码的。为什么父进程都是bash?因为bash怕代码有问题,如果直接给bash来跑代码,那么挂了bash也就挂了,所以bash来创建子进程避免受其他程序影响。
杀掉bash进程命令:kill -9 PPID
杀掉子进程命令:kill -9 PID
如何创建的子进程呢?
题外话:批量化注释操作:首先在命令模式下ctrl + v(进入v-block模式)然后j选择要注释的区域,然后输入切换成大写,然后输入i,然后//注释指定一行,最后按ESC就完成了批量化注释,如果要取消直接小写u撤销
fork系统调用接口:作用是创建一个子进程。pid_t fork(void);
demo
这里fork出来的子进程就是12654,它的父进程就是12653,12653的父进程就是bash就是6011
fork的返回值
成功创建子进程,则子进程PID被返回给父进程,0被返回给子进程。创建失败,-1被返回给父进程,没有子进程别创建。
再次说明上述的问题:
为什么一个函数会有两个返回值?
为什么一个变量会存放不同的数据?
一般使用fork的方式
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <assert.h> int main() { pid_t ret = fork(); assert(ret != -1); //创建失败 if(ret == 0) //子进程 { while(1) { printf("我是子进程, 我的PID:%d 我的父进程PPID:%d\n", getpid(), getppid()); sleep(1); } } else if(ret > 0) //父进程 { while(1) { printf("我是父进程, 我的PID:%d 我的父进程PPID:%d\n", getpid(), getppid()); sleep(1); } } else //异常情况 {} return 0; }
再来回答如何创建子进程这个问题:
fork之后,执行流会变成2个执行流;fork之后,父进程和子进程谁先运行是不确定的,谁先运行是由调度器决定;fork之后,fork之后的代码是被双方共享的,通常我们通过if和else if来进行执行流分流。
fork原理
进程 = 内核数据结构 + 该进程的代码和数据;创建子进程并不是又多了一份代码和数据,而是多了一个PCB,也就是数据结构对象(结构体)。调用fork就是创建一个PCB数据结构对象。这就说明父进程和子进程用的是同一段代码。这里的父子进程是具有独立性的(也就是一个进程关闭,另外一个进程不受影响)
父子进程互相不受影响的证明
那么,为什么一个函数会有两个返回值?
首先代码只有一份,代码是只读的。
现象
数据是当有一个执行流尝试修改数据的时候,OS会自动给我们当前进程进程触发写时拷贝机制,也就是数据重新拷贝一份,你要修改就在拷贝的空间里面修改数据。这里的父进程本来第一次是100,但是这里父进程第一次走完后,修改了数据,但是是写时拷贝,修改的不是原来空间的数据,所以子进程依旧是100,并没有修改。
很奇怪,一个函数中return语句使用只要执行不就直接返回了吗,那么为什么一个函数会返回两个值呢?这就很错视觉。fork本质就是OS提供的一个函数,既然是函数那就有return返回返回值,这里fork是创建子进程,
有父进程和子进程,那么父进程就有返回,子进程也有返回,所以有两个返回值(初步理解)。那么接受用的是一个变量,那么怎么可能接受变量只有一个而出现两个不同的值呢?很错视觉。想想这个问题?既然是变量,那么就有空间,那么数据就会被覆盖:比如:int a = 10; a = 20; a= 30; 再最后输出a的候,a=30。