赞
踩
目录
计算机都是由硬件组成的
- 输入设备:键盘,磁盘(外设),网卡,显卡,话筒,摄像头等
读取文件就是从磁盘读取文件
- 输出设备:显示器,磁盘,网卡,显卡,音响等(ms/s级别)
输入输出并不是独立的两套设备,比如磁盘,从磁盘读设备/向磁盘写数据
s秒级别的比如说网卡,别人给你发送数据,要等几秒。
- 存储器(内存)
离cpu越近,越贵越小越快
- 运算器和控制器(中央处理器CPU)ns级别
是否可以不存在内存?直接将数据写入cpu中?
cpu的成本太高,虽然他很快但是越快的设备它是越贵的,并且他的存储体量也很小。
所以数会从输入设备存到存储器(内存)中,当cpu空闲的时候将数据交给cpu处理,处理好后再将数据传给存储器,再由存储器从输出设备传输。这样就避免了cpu直接接触外设
所以一个计算机的好坏取决于存储器(内存),因为计算机要把所有数据都交给内存。
内存是体系结构的核心设备
【总结】:
- 这里的存储器指的是内存
- 不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)
- 外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。
- 所有设备都只能直接和内存打交道
当两个不同地方的人是如何发消息的呢?
结论:任何外设,在数据层面,基本优先和内存打交道,CPU在数据层面上也直接和内存打交道
任何计算机都包括一个基本的程序集合,称为操作系统(OS),而操作系统在启动下才有意义,因为要将数据与代码加载到内存中,那么OS是什么?OS就是一款专门针对软硬件资源进行管理工作的软件。再整个计算机硬件架构中,操作系统的定位是:一款纯正的“搞管理”的软件。
概括的理解,操作系统包括:
管理分为管理者和被管理者,而管理包括作决策的和执行者
举例子校长就是典型的管理者,辅导员是执行者,学生就是被管理者
那么操作系统就是管理者,它并不直接管理硬件或软件,而是通过执行者来对用户和硬件进行管理,用户操作接口和驱动程序就是执行者。
那么从校长的角度:
1.如何聚合同一个学生的数据?
通过数据结构,其中的结构成员可能包括学生的姓名,年龄,性别,学分等等
2.如何将多个学生的聚合数据产生关联
将学生的数据放到数据结构中来管理,比如队列,二叉树这种。本来对学生的管理工作,变成了,对数据结构的增删查改。
- 对以一个被管理对象,先描述他的结构,再组织他的特性,将被管理对象使用特定的数据结构组织起来。所以管理的理念,先描述再组织,再组织可以转化对目标的管理,转化成为对数据的管理。
那么如何管理进程呢?先描述,再组织。描述进程是通过描述进程的结构体,这个进程控制结构体叫做进程控制块(PCB)。
操作系统:对下管理好软硬件资源,对上为用户提供良好的运行环境-普通用户-程序员-为程序员提供各种基本功能。
然而操作系统并不相信任何用户,但是它还要给用户提供服务,这就相当于银行,银行本身是不信任你的,但是他依旧会给你提供服务,但是不会直接提供,而是通过银行柜台来间接提供服务。所以操作系统并不会直接向用户提供服务,而是通过系统调用接口来给用户提供服务,叫做OS提供的接口。
【问题】:系统中中可不可能存在大量的进程?可能
是操作系统在管理进程吗? 是的
如何管理呢?先描述,再组织
用什么描述呢?此时描述作为一个结构体,我们称之为PCB,进程控制块。
为什么要有PCB?任何进程再形成之时,操作系统要为该进程创建PCB
进程信息被放在一个叫做进程控制块的数据结构当中,可以理解为进程属性的集合。操作系统层面上,PCB就是一个结构体类型。在Linux系统中,PCB的形式为struct task_struct{}在这个结构体中,包含了进程所有的属性。PCB是属性的统称,那么struct task_struct就是具体表述这种属性的结构体。
当运行这个程序,用命令行查看发现运行程序的时候,myproc程序存在pid,而停止运行程序,myporc.c程序的pid也不存在了。
- [wjy@VM-24-9-centos test_3_21] cat myproc.c
- #include <stdio.h>
- #include<unistd.h>
-
- int main()
- {
- while(1)
- {
- printf("hello world!\n");
- sleep(1);
- }
- return 0;
- }
对于加载到内存的可执行文件和描述进程的结构体task_struct我们统称为进程,而tast_struct由操作系统自动创建,总的来讲进程=程序+操作系统维护进程的相关数据结构
代码和数据加载到内存后会形成一个一个的task_struct,操作系统并不会对代码和数据进行管理,而是对代码和数据所对应的控制块进行管理。
1.标识符:描述本进程的唯一标识符,用来区别其他进程。pid
那我们来验证一下pid,获取pid要包含头文件,这个是系统头文件
- [wjy@VM-24-9-centos test_3_21]$ cat myproc.c
- #include <stdio.h>
- #include<unistd.h>
- #include <sys/types.h>
-
- int main()
- {
- while(1)
- {
- printf("hello world!: pid: %d\n",getpid());
- sleep(1);
- }
- return 0;
- }
当一个进程执行,发现他的标识符pid是14793,用ps axj命令也验证了这一点。
除了ctrl+c可以结束进程,还有一个命令 kill -9 "pid"
除了子进程pid还有父进程ppid,用getppid获取
printf("hello world!: pid: %d, ppid: %d\n",getpid(),getppid());
这个ppid就是bash
在程序运行退出的时候总有一个return 0,那么这个就是退出码,通过echo $?查看退出码为100(因为代码程序已经将退出码改为100),但是第二次使用echo $?命令发现退出码变成0,这是因为echo $?命令会显示最近一条命令程序的退出码,第二次echo $?显示的是上一次用echo $?的退出码,为0.
- [wjy@VM-24-9-centos test_3_21]$ cat myproc.c
- #include <stdio.h>
- #include<unistd.h>
- #include <sys/types.h>
-
- int main()
- {
- while(1)
- {
- printf("hello world!: pid: %d, ppid: %d\n",getpid(),getppid());
- sleep(1);
- }
- return 100;
- }
2.状态:任务状态,退出码,推出信号等
上面提到了退出码,运行程序时,会将程序和数据记录到pcb当中,同样退出码也会记录到pcb,然后让其他进程来获取这个状态。
所以task_struct中已经包括了
- struct tack_struct
- {
- int pid;//进程标识符
- int code,exit_code;//退出码
- int status;//状态
- }
3.优先级:优先级和权限的区别
4.程序计数器:程序中即将被执行的下一条指令的地址,当操作系统执行完指一条指令,指针会自动跳转到下一个要执行的语句。
5.内存指针:包括程序代码和进程相关的数据的指针,还有和其他进程共享的内存块的指针。系统通过内存指针指向的这个信息来找到对应的数据信息,也就是说,系统通过内存指针指向的pcb找到对应的代码和数据。
6.上下文数据:进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]
首先我们要知道,寄存器是存放当前正在运行的进程,平时我们用电脑的时候好像很多进程都同时运行,查看系统的任务管理器会看到,很多程序在运行,但是他们并非真正的同时运行,而是将数据和代码上传到进程控块中(task_struct),这些task_sturct组成一个队列,进行等待。
当一个进程需要5s来运行,进程的代码可能不是很短时间就能运行完毕的,cpu并不能真的分配5s,而是通过时间片,规定每个进程单次运行的时间片,比如5ms,进程1的task_struct运行完这5ms后就在队列后面排队,进程2开始运行,过了这5ms就在进程队列后面排队,如果这个进程的总消耗时间小于5ms,那么这个进程不再排队。
所以在单CPU情况下,用户感受到的多进程同时在运行,本质是通过CPU的快速切换完成的。并且进程是以时间片来运行的。进程可能存在大量的临时数据
当进程1走完这个时间片的进程,需要在队列后面排队,但是走的时候需要将已经运行的进程信息保存下来,以便下次继续运行时候衔接上次运行的结果继续运行,这就叫保护上下文。虽然寄存器只有一个,但是寄存器里的数据是你这个进程的。 当这个队列又轮到进程1来运行,进程将task_stuct中保存的上下文继续交给寄存器来运行,直到5ms结束后,再次将上下文保护起来。将上下文交给寄存器运行的这个过程,叫做恢复上下文。
所以保护和恢复上下文是为了让你去做其他的事情,但不耽误当前的事情,并且当你想回来继续学习的时候,可以接着之前你的学习内容继续学习
所以上下文就是:进程执行时所形成的处理器寄存器当中与进程强相关的临时数据
通过上下文,我们能感受到进程是被切换的。
7.I/O状态信息:包括显示显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表,将进程写入写出的操作
8.记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等
进程的信息可以通过/proc系统文件夹查看
蓝色部分字体是进程id,也就是说进程启动之后,会在proc目录下形成一个以自身pid编号为目录文件名的文件夹
当执行一个文件时,查看他的进程,就是查看在proc目录下的pid目录文件内容
通过ls /proc/pid -al命令可以查看到,exe对应的就是执行该文件的路径,cwd是当前工作目录
当在myproc.c中写了这样一个程序,发现在该程序的当前路径下出现了要写入的log.txt文件,最初是没有log.txt文件的。但是在写入这个程序后,该路径又出新了log.txt,我们并没有指明该文件的路径,该文件是如何生成在此路径下呢?
进程通过cwd找到当前路径,然后在这个路径下创建文件。
- [wjy@VM-24-9-centos test_3_21]$ cat myproc.c
- #include <stdio.h>
- #include<unistd.h>
- #include <sys/types.h>
-
- int main()
- {
- FILE* fp = fopen("log.txt","w");
- fclose(fp);
- while(1)
- {
- printf("hello world!: pid: %d, ppid: %d\n",getpid(),getppid());
- sleep(1);
- }
- return 0;;
- }
-
- [wjy@VM-24-9-centos test_3_21]$ ./myproc
- hello world!: pid: 9042, ppid: 6957
- hello world!: pid: 9042, ppid: 6957
- hello world!: pid: 9042, ppid: 6957
- ^Z
- [1]+ Stopped ./myproc
- [wjy@VM-24-9-centos test_3_21]$ ll
- total 20
- -rw-rw-r-- 1 wjy wjy 0 Mar 29 22:05 log.txt
- -rw-rw-r-- 1 wjy wjy 64 Mar 21 11:20 Makefile
- -rwxrwxr-x 1 wjy wjy 8616 Mar 29 22:05 myproc
- -rw-rw-r-- 1 wjy wjy 240 Mar 29 22:04 myproc.c
-
fork有一下特点:
通过下面三个代码发现,第一个hello world显示了两次,第二段代码中,以前我们写if else语句,二者只能执行一个,但是绝对不能执行两个,但是这次两个都执行了。而第三个在执行fork之后,执行的printf死循环语句中两个进程居然不一样。
通过这些奇怪的现象,本质是fork之后,有两个执行流。
ps:在vim模式下怎么查看手册?
普通模式下,直接输入man + fork命令就能查看手册,在vim模式下,要在esc模式下,“:” + man +fork 查看命令
同样,如果写了makefile文件,想要在vim下直接编译,就是在esc下“:”再加上该有的命令
当写这样一个进程代码,fork下会执行两次cout语句,第二条语句的父进程就是第一条语句的紫禁城,那么第一条语句的父进程就是bash,7758进程就是7759进程的爹
那么如何理解fork创建子进程?
1.通常我们创建进程就是./cmd和执行命令,那么fork又是一种创建进程的方式。但在操作系统角度,这几种创建进程方式没有差别。
2.当fork创建了进程,系统里就多了一个进程,那么就多了一个与进程相关的内核数据结构+进程的代码和数据的PCB块,内核数据结构就是task_struct,进程的代码和数据是谁呢?在第一个进程执行完有了task_struct和代码+数据,那么子进程的代码和数据从哪来?
fork创建子进程后,默认情况下,会“继承”父进程的代码和数据,内核数据结构task_struct也会以父进程为模板,初始化子进程的task_struct。也就是说fork之后子进程的描述的数据结构(PCB)是跟父进程一样的,里面的代码和数据也是以父进程为模板或者继承父进程。
fork之后,子进程和父进程代码是共享的。那么子进程的代码是不可修改的,父子进程代码只有一份。那么默认情况下,数据也是共享的。在我们的操作系统中,有时候会同时打开很多软件,当一个软件关闭后,不会影响另一个软件,说明进程具有独立性。但是数据也要考虑修改的情况,当父子进程只有读操作时候,他们的数据是共享的,如果要修改一个进程的数据,通过“写时拷贝”来完成数据的独立性。所以写时拷贝是为了维护进程的独立性,让各个进程不被干扰。
但是如果没有写入操作,也就是修改操作的时候,我们是不需要进程写时拷贝,共享一份数据即可。如果只读不写,写时拷贝会造成空间的浪费
那么我们创建子进程就是为了跟父进程干一样的事情吗?一般是没有意义的,通常父子进程要做不一样的事情。
父子进程一般是靠fork的返回值来完成的!
这样进程中有两种返回值,因为一个id里面有两个结果。但是从前没有出现有两种返回值的情况,但是在fork之后又两种结果返回值。
如何理解有两个返回值?
两个返回值代表两个数据,如果一个函数已经开始执行return,说明函数的核心功能已经执行完了,在上面图中的代码中,fork()执行后,会返回一个id,这也是返回值,先执行父进程的返回值,因为父进程先改变,所以先进行了写时拷贝,也就是说,返回值也是数据,return的时候会进行写入,发生了写时拷贝。这也就证明了return语句也是语句!
如何理解两个返回值的设置
父进程与子进程是1:n,一对多的关系
所以父子进程可以通过if,else分流来做不同的事情,通过下面这段代码,发现子进程的父亲就是父进程的进程
- int main()
- {
- pid_t id=fork();
- if(id==0)//子进程
- {
- while(true)
- {
- std::cout<<"I am child:pid:"<<getpid()<<",ppid:"<<getppid()<<std::endl;
- sleep(1);
- }
- }
- else if(id>0)//父进程
- {
- while(1)
- {
- std::cout<<"I am parent:pid:"<<getpid()<<",ppid:"<<getppid()<<std::endl;
- sleep(1);
- }
- }
- else
- {
-
- }
- sleep(1);
- return 0;
- }
通过ps axj | grep myproc发现这两个进程在运行
那么fork之后,父子谁先运行?这个是并不确定的,父子的运行是通过调度器来控制谁先后运行
在操作系统中,进程状态的意义是为了方便操作系统快速判断进程,完成特定的功能,比如调度,本质是一种分类。进程状态的信息一般放在task_struct(PCB)中。
为了弄明白正在运行的进程是什么意思,我们需要直到进程的不同状态,一个进程可以有多种状态,在Linux内核里面,进程有时候也叫做任务。
具体状态:
运行状态的资源不一定占用CPU,有时候会在一个等待队列中,呈现运行状态,等待被调度。这个队列也就是task_struct,描述进程的控制块组成的队列,里面放的都是每一个要执行进程的代码和数据。这时候对进程的调度也就变成了CPU对task_struct的增删查改。
当我们像完成某种任务的时候,任务条件不具备,比如先从磁盘读入数据,但是磁盘已经满了;想从网络读取数据,网络断了等等这种任务条件不具备,需要进程进行某种等待,需要S或D。
和等待运行队列一样,等待进程会形成一个等待队列,在缺乏条件的时候,这种不可被直接调度的进程,进入等待队列,但是等待队列放的不是代码和数据,而是进程控制块,R状态的队列直接放代码和数据,而阻塞状态队列放PCB,再通过PCB来寻找代码和数据。
需要注意的是,进程不止会去等待CPU资源。等待CPU资源的是R状态,等待其他设备的时候是S/D状态。当S/D状态下的某个队列中的进程条件就绪,等到了磁盘这种设备,不会立马被CPU调度,而是从S/D状态变为R状态进入R中的队列,等待CPU被调度。
所以进程在运行的时候,有可能因为运行需要,可能会在不同的队列里。在不同的队列里所处的状态也是不一样的。
我们把,从运行状态(run_queue),放到等待队列中,就叫做挂起等待(阻塞);从等待队列放到运行队列,被CPU调度就叫做唤醒进程。系统中一般存在大量阻塞队列,少量运行队列
那么什么是D不可中断睡眠呢?
当一个进程要往磁盘中写入数据,但是此时磁盘并没有时间来做这个进程,磁盘让进程在内存中等待。此时磁盘对进程说,你等我一会,等我把现在的事情忙完,我就执行你的进程。此时进程在内存中等待,磁盘去做别的事情了。此时操作系统会对进程进行管理,看到有进程什么事情都没有做,系统中的资源本来就不够,进程还在占着茅坑不拉屎,这时OS将等待的进程释放。但是等磁盘昨晚当前进城后,回头找刚才等待的进程,发现刚才等待的进程没有了。因为磁盘运行完上个进程无论成功还是失败,都需要对下一个进程返回一个状态,但是此时进程却没有了。
根据以上描述,为了让等待的进程不被操作系统释放,有了不可中断睡眠D。
所以有两种状态睡眠,可以被杀掉的睡眠S,和不可被杀掉的睡眠D。
一旦进程进入死亡状态,操作系统要回收它的资源,回收进程资源=进程相关的内核数据结构 + 它的代码和数据,当我们创建进程,会创建描述进程的进程控制块以及它的代码和数据,那么释放的也是释放这两种。
死亡状态很难被查到,就在一瞬间,PCB已经被释放。
辨别退出死亡的原因!给出进程退出的信息,也是数据。
那么当进程退出的时候,它的资源并不是立即被释放,而是先进入僵尸状态,将进程退出的所有原因写进PCB的task_struct描述数据结构中,让系统和父进程进行读取,此时的task_struct被称为僵尸状态。所以正常的死亡状态是Z->X->被释放。
一个进程如果不会收,会让进程一直处于僵尸状态,僵尸状态的进程不释放会占用资源,形成内存泄漏。
如果父进程没有检测或回收进程,该进程就会进入僵尸状态Z。为什么会有子进程都停止了,而父进程不回收它的情况?
下面这段代码当中,先创建子进程,两个进程可同时运行,我们知道进程结果为0的是子进程,结果不为零的是父进程,所以我们可以设置父进程等待50秒运行一次,子进程2秒运行一次,父进程在这50秒的时间内会是等待状态,什么都不干。如果把子进程杀掉,父进程处于等待状态,使得被杀掉的子进程没有被回收,变成了僵尸状态。
如果父进程被回收后,子进程如果想被回收,操作系统会想方设法去回收子进程,但是这样的父进程在等待状态,也没有被回收的情况下,父进程就不会管子进程,操作系统也没有办法回收子进程,这样子进程变成僵尸状态。
- int main()
- {
- pid_t id=fork();
- if(id==0)
- {
- //child
- while(true)
- {
- cout<<"I am child,running!" <<endl;
- sleep(2);
- }
- }
- else{
- //parent
- cout<<"father do nothing!"<<endl;
- sleep(50);
- }
-
- return 0;
- }
但如果子进程还在运行,父进程已经挂掉。这样的话子进程就没有人回收了,这时候子进程就变成孤儿进程,它会被1号进程领养,也就是它的ppid变为1,这个1号进程就是操作系统。所以当孤儿进程被杀掉要被回收的时候,1号操作系统进程就来回收这个孤儿进程。
进程是一直在RS状态之间切换,因为外设访问CPU实在太慢,对于CPU而言,我们外设访问的没有CPU运行快,cpu很大程度上都在等待
如果把一个进程暂停,那么是不是也有暂停状态呢?
那怎们暂停呢?
使用kill -l命令查看,发现18和19两个分别是继续进程和暂停进程
当运行进程,输入kill -19 +进程号,就可以暂停一个进程,通过ps axj | grep myproc查看进程的pid,输入命令kill -19 进程号,输入后发现进程变为T状态。
再让它继续运行,那么输入kill -18 +进程号,将暂停的进程继续运行,当我们运行再次查看进程号发现,进程号的状态后面没有+了,而且按ctrl+z这样的退出信号也不会退出,这是因为,只要运行了暂停命令,这个进程就在后台运行,那么该如何杀掉进程?
杀掉进程:kill -9 进程号。
当我们直接运行一个进程,这样的进程是在前台运行的,这样的进程用ps axj | grep mygrep(mygrep是我写的一个文件)命令后,查看到的进程状态后面是带一个“+”的,典型特征是按什么键都不好使,但是ctrl+C/Z后,进程停止。
或者输入fg让后台进程提到前台运行,之后再ctrl+C杀掉进程
如果在进程运行命令后面加一个取地址,这个叫做后台命令,虽然也在屏幕当中刷新。它的典型表现是可以执行你的命令,但是ctrl+C/Z命令不好使。
[wjy@VM-24-9-centos 330test]$ ./myproc &
当我们写一个程序并将它运行,用ps -l命令查看,就会查看当前运行程序的信息。里面的PRI就是优先级,我们可以看到,这个进程的优先级是80
- [wjy@VM-24-9-centos 330test]$ cat myproc1.c
- #include <stdio.h>
- #include <unistd.h>
-
- int main()
- {
- while(1)
- {
- printf("I am a process,pid:%d,ppid:%d\n",getpid(),getppid());
- sleep(1);
- }
- }
这其中有几个和重要的数据
调整优先级就是调整nice值,更改方法:
但是这个优先级不是所有的数字都能写的,如果超过-20 - 19 这个范围,大于19,都视为19,小于-20都视为-20.
但是第二次改优先级的时候发现,本来new_priority=old_priority+nice,第二次改变优先级本来值应该在90的基础上加5为95,但是第二次改变值是85.所以不管第几次修改,old_priority值都是从80开始。
进入top命令修改
按r输入进程号,再输入修改的值,按q退出
用ps -al命令查看,修改成功 ,把优先级降低
但是优先级这个东西我们自己最好不要改。
nice值为什么是一个比较小的范围呢?
优先级的设置,只能是一种相对优先级,不能出现绝对的优先级。如果范围很大,能随意修改进程优先级,那么导致不调整优先级的进程长时间不能被执行,会出现很严重的进程“饥饿”问题。
调度器的功能:较为均衡的让每个进程享受到CPU资源。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。