本文重点:
1.了解冯诺依曼体系结构;
2.操作系统的概念与定位;
3.进程的概念及其相关操作;
4.进程的创建;
5.进程的状态及如何修改进程状态;
一.冯诺依曼体系结构:
冯诺依曼体系结构是现代计算机的硬件体系结构;
我们常见的计算机:如笔记本电脑,台式电脑以及服务器都遵循冯诺依曼体系结构;
冯诺依曼体系结构图如下所示:
1.组成:(现代计算机的五大硬件单元)
输入设备:指键盘,鼠标,扫描仪等;
输出设备:显示器,打印机等;
存储器:指的是内存;
(中央处理器CPU由控制器和运算器组成)
运算器:进行数据处理;
控制器:控制震荡周期,主频越大,一秒钟执行的及其指令就越多,则处理数据的速度就越快;
2.工作原理:
用户通过输入设备将数据传输进来,经过内存对数据进行缓存,然后交给CPU进行处理数据,当数据处理完毕后CPU再次交给内存缓存,然后通过输出设备将数据输出到显示器上方便用户接受数据;
硬件结构决定软件行为,所有的硬件都是围绕存储器进行工作的;
3.注意点:
(1)外设(输入或输出设备)要输入或者输出数据,只能写入内存或者从内存中读取;
(2)不考虑缓存的情况,cpu也只能和存储器打交道;
(3)内存是一个易失性介质,一旦断电内存中的文件将会丢失;因此内存的容量不会过大;
二.操作系统 (operator system)
1.操作系统的定位:
管理软硬件资源,提供良好的执行环境,让计算机更好用;
2.如何搞管理:先描述,后组织;
(1)对于硬件来说,一个硬件插入电脑上就是安装硬件的驱动程序,然后操作系统就可以通过驱动程序来获取底层硬件信息;
(2)对于软件来说,操作系统会提供一套接口————系统调用接口,用户不可能直接调用系统调用接口,而是在系统调用接口上有很多的库函数对其进行封装,如:shell, lib库函数;再进行调用,实现管理软硬件资源;
注意:库函数与系统调用接口的关系:库函数是对系统调用接口的一种封装;
管理者并不需要直接与被管理者交互进行管理,而是通过对被管理者进行描述,并且将描述信息得当组织起来进行管理;
三.进程概念:
1.什么是进程??
在用户的层面:进程就是一个运行中的程序;
在操作系统层面:程序在运行的过程中需要将数据和代码加载到内存中;因此就需要去管理这些程序的运行;在操作系统层面上,进程就是操作系统对一个运行中的程序的描述;(相当于校长管理学生看到学生的档案就相当于看到了学生本人,通过档案来管理学生)
在操作系统眼中看到了对运行程序的描述就仿佛看到了进程,通过描述来管理进程,这个描述信息叫PCB(进程控制块);Linux下叫struct task_struct结构体;因此对于操作系统来说,进程就是PCB;
2.操作系统管理进程:
(1)操作系统通PCB来管理进程;
具体过程:CPU不会一个程序一个程序处理,而是会一次处理多个程序;但在同一时间只会处理一个程序,因此需要通过快速切换来运行程序,使多个程序可以同时运行,在此期间的运行时间是非常快的;当CPU处理一个程序,在某一时刻会切换到另一个程序,而此时该程序被CPU处理的信息将会记录到PCB上;
即每一个程序都有一个PCB来描述这个程序,分别指向内存中的数据与代码,CPU进行调度的时候只要拿到PCB就知道上次该程序运行到哪一块,这个进程运行的数据和代码在内存中的哪一块;此时就可以保存此次运行到该位置的数据和代码,等到下一次运行到这里时将数据和代码重新加载到寄存器中继续运行;(中间通过操作系统来进行控制)
(2)CPU的分时机制:对程序运行处理进行切换调度处理;
(3)CPU的分时计数:CPU同一时间只能处理一个程序,但CPU通过快速切换来运行程序使程序可以同时运行;
(4)时间片:CPU在每个程序上运行的很短的时间段称为时间片;
(5)PCB中的描述信息:
内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享内存块的指针;
程序计数器:程序即将被执行的下一条指令的地址;
上下文数据:进程执行时处理器的寄存器中的数据;
标识符:跟这个进程相关的唯一标识符,用来区别其他进程;
进程状态:如果程序正在执行,那么进程处于执行态;
进程优先级:决定了这个进程能获取我们的CUP的优先分配权;优先级为了让操作系统运行的非常合理;(交互式进程—用户体验一定要快)
记账信息:记录进程的运行时间等等;
IO信息:打开什么文件,搞了什么操作…;
3.如何查看进程:
(1)查看所有进程:
ps -aux
ps -ef
(2)查看指定进程:
ps -aux | grep + 生成文件名
ps -ef | grep +生成文件名
//vim loop.c编辑一个程序;
//写一个简单的hello world程序,运行程序,d打印其程序的ID并进行查看;
#include <unistd.h>
#include <stdio.h>
int main(){
pid_t pid=getpid();
while(1){
printf("hello world!! %d\n",pid);
sleep(1);
}
return 0;
}
通过命令查看指定的进程;
程序执行结果:
由两张图可知:该程序的进程ID就是5859;
(3)根目录下的proc查看进程信息:
/proc/
四.如何创建进程:
1.进程创建调用fork()接口:
fork接口用于创建进程; pid_t fork(void);
原理: 通过复制调用进程,创建一个子进程;子进程复制的就是父进程的pcb(因此父子进程数据独有,代码共享)
2.子进程创建的意义:
(1)子进程是另一个独立的个体,不希望和父进程处理相同的事;因此可以分摊任务处理压力;
(2)让子进程完成其他工作—背锅;
3.如何让子进程完成其他任务—如何分辨父子进程???—通过返回值不同
对于父进程来说,fork返回值是子进程的pid;创建子进程失败返回-1;
对于子进程来说,fork返回值是0;
用户通过fork()的返回值不同对父子进程运行流程进行分流;
举个栗子:
//我们写一个程序来分辨父子进程及其关系;
//在creat.c文件下;
#include <unistd.h>
#include <stdio.h>
int main(){
printf("hello world ------pid:%d\n,getpid());
pid_t pid=fork(); //创建一个进程;
if(pid<0){
printf("fork error\n");
}
else if(pid==0){
printf("----i am child-----pid:%d\n,getpid());
}
else{
printf("----i am parent----pid:%d\n,getpid());
}
printf("nihao~~ pid:%d\n",getpid());
while(1){
printf("very good!!\n");
sleep(1);
}
return 0;
}
程序执行结果如下:
我们发现:子进程不是从main函数的开头开始运行的;它是从fork之后(即内部子进程创建成功后)开始运行的;
通过不同的返回值可以分辨父进程和子进程;6430为父进程的id;6431为子进程的id;
五.进程的状态:
1.分类:
(1)运行状态(R):进程一定在运行中,它表明进程要么是在运行中要么在运行队列里;
(2)可中断休眠状态(S):意味着进程在等待事件完成;
(3)不可中断休眠状态(D):在这个状态的进程通常会等待IO的结束;通过特定的方式才能唤醒;
(4)停止状态(T):可以通过发送 SIGSTOP 信号给进程来停止进程;这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行;
(5)僵死状态(Z):
2.僵尸进程:处于僵死状态的进程;
(1)僵尸进程产生原因:
子进程先于父进程退出,因为要保留退出原因,因此操作系统不能直接释放其所有资源,于是操作系统通知父进程获取退出原因,允许操作系统释放资源,但父进程没有关注到这个通知,导致子进程退出后无法释放所有的资源,处于僵死状态成为僵尸进程;
(2)僵尸进程的危害:
a.进程的退出会一直被维持下去,如果父进程不管,子进程一直保持僵尸状态; b.维护退出状态本身就是要用数据维护,因此就会保存在task_struct中;即僵尸状态一直不退出PCB就会一直维护;
c.造成内存资源的浪费;因为数据结构本身就是要占用内存资源;
d.造成内存泄漏;
(3)处理方法:关掉父进程;进程等待;
实例:
我们将上面的程序再次运行起来:
然后再开一个通道查看该进程:
可以看到6678是父进程,6679为子进程;
当我们杀死子进程时,此时子进程变为僵尸进程:
(如图所示:子进程显示的状态是Z+;说明此时已经是僵尸进程)
杀死父进程可处理该僵尸进程:(整个过程)----此时的程序也停止运行了;
3.孤儿进程:
孤儿进程产生原因:父进程先于子进程退出,父进程退出后,子进程成为后台进程,并且父进程成为1号进程;