目录
一 冯诺依曼体系
二.操作系统
三.进程
四 .创建进程及fork的使用
五.进程状态
我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系。(下面是一张冯诺伊曼结构的图片):
1.存储器:对应我们自己电脑中的内存。
2.输入设备:包括键盘,鼠标,硬盘等。
3.输出设备:硬盘,显示器(注意输入设备和输出设备都就做显示器)。
4.中央处理器:由运算器和控制器两部分组成。
从上面的这张图片我们也能知道:
1.外设不是直接和cpu进行交互,而是先与内存交互在与cpu交互,这是因为cpu的运算速度是特别快的为了平衡这个差距会存在一个中介来平衡一下。
2.有了内存cpu不需要直接和外设进行交互。
3.读取数据时,输入设备将数据写入到内存当中,然后内存把数据交给cpu,让cpu处理数据
,cpu处理完成之后把数据写回到内存当中,最后内存把数据写入到输出设备当中。
1.什么是操作系统:
操作系统(Operation System, OS) 是指控制和管理整个计算机系统的硬件和软件资源,并合理的组织和调度计算机的工作和资源的分配,以提供给用户和其它软件方便的接口和环境,它是计算机系统中最基本的系统软件。
2.为什么要要操作系统:
1.对上为用户或者程序员提供稳定的高效和安全的的运行环境,为程序员提供各种功能。
2.对下管理好各种软件和硬件资源。
如果进行管理?在学校里面管理学生,首先我们是被辅导员管理起来,然后辅导员又是被院领导管理起来,然后院领导又被校长管理起来。即 学生-> 辅导员->院领导->校长这样一个结构。院领导如果管理我们呢?院领导不需要认识我们就可以对我们进行管理,院领导知道我们的学号和姓名已经其他信息就可以管理一个学生在将所有的学生用某种结构组织起来这样对一个学生的管理就变成了对这种结构的增删查改等操作,即先描述在组织。操作系统管理软件和硬件也是这样管理的先描述在组织。
系统调用和库函数概念:
系统调用:
在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。库函数:
系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发。
1.进程的相关概念。
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是 操作系统 结构的基础。 在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。----来自百度。
2.前面我们说了学校如何管理学生,程序加载到内存当中变成程序操作系统就需要对其进行管理,管理进程就需要先描述在组织。
当进程加载到内存之后,操作系统就要对其进行管理,如何管理?先描述在组织?如何描述呢?操作系统会给一个进程创建进程控制块来管理他,名字叫做PCB,在linux当中PCB是task_struct (Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息)。
3.简单理解一下进程=程序所对应的代码+数据+内核数据结构当然还有页表和进程地址空间后面在提及。
2.进程的相关内容
task_struct里面有那些内容呢?
1.标示符: 描述本进程的唯一标示符,用来区别其他进程(就像我们的身份证一样是独一无二的)
在这里解释一下上面这些是什么意思:
PPID:父进程的id PID:子进程的进程号 UID:用户id
2.状态: 任务状态,退出代码,退出信号等(可以使用echo $?查看)
4.程序计数器: 程序中即将被执行的下一条指令的地址(pc指针)
5.内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针(方便PCB找程序对应的代码和数据)
6.上下文数据: 进程执行时处理器的寄存器中的数据在单核CPU下进程运行需要在运行队列中排队,等待CPU调度,比如说一个进程需要10ms才能执行完毕,CPU是要等这个进程执行完了才去执行其他程序吗?当然不是CPU只会让这个程序跑一段时间,时间片到了它就要从CPU下来,让其他的进程上来实现进程之间的切换但是等到这个进程再次上来的时候,发现完了我不知道我上次执行到哪了,所以进程从CPU下来的时候需要将临时数据放到task_struct中带走,这样下次它被CPU调度的时候就知道自己上次执行到哪里了。
7.I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
8.记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
9.其他信息.
3.组织进程
linux下如何组织进程呢?将一个一个的task_struct 一链表的形式组织在一起,对进程的管理就相当于对链表的管理。
1.在linux下我们如何查看进程呢?
方式一:ls/proc.
方式二:
ps ajx 指令:
使用这条命令会将所有的进程全部显示出来这样我们看起来就很难受所以我们如果需要找某个进程可以使用 管道+grep查找特定的进程。
2.如何获取进程的标识符
1.getpid(获取进程pid)
2.getppid(获取父进程的id)
通过man getpid查找
我们将这个程序跑起来:
注意:普通进程的父进程为bash,我们可以使用ps命令进行查看:
3.通过系统调用创建子进程
我们先通过man 手册查看这个函数:
功能:以当前进程为模板创建一个子进程
返回值:fork函数有两个返回值。
1.给父进程返回子进程的id
2.给子进程返回id(成功返回0,失败返回 -1)
下面我们通过一个段代码验证:
1 #include
2 #include 3 using namespace std; 4 #include 5 #include 6 int main() 7 { 8 pid id =fork(); 9 printf("pid 为:%d ppid为:%d\n",getpid(),getppid()); 10 sleep(1); 11 return 0; 12 } 运行这个程序:
我们进程的发现我们只有一条语句为什么会执行两次,很多铁子会说这不科学?这到底时为什么呢?
这是因为我们以前的在windows下写的代码都是单执行流,在linux中父进程传创建子进程成功后,进程具有独立性会分别执行fork的代码所以打印语句会执行两次。同样的解释下面几个问题
1.为什么fork会有两个返回值:
在fork函数中函数return之后子进程就被创建出来,同时执行return语句。会和父进程一样到运行队列中等待CPU调度,父进程和子进程代码和数据共享也就是子进程以父进程为模板,但是当返回id写必须会写入此时也必然会发生写时拷贝,虽然名字相同但是内存地址是不相同的。
2.如何理解创建进程
创建一个进程,那么就意味着系统会多一个进程,操作系统是做管理的软件既然多了一个进程那么就需要对其进行管理,而管理要先描述在组织,所以操作系统会创建task_struct 管理这个进程,子进程在创建的过程中以父进程为模板就像我们会继承我们爸爸的一部分基因,代码和数据在不修改的时候是共享的,一但发生修改会发生写时拷贝各自一份,具有独立性。
3.为什么要给父进程返回子进程的pid,子进程返回0.
一个父进程可以有多个子进程,而父进程只有一个所以需要子进程创建成功时给父进程返回自己的id,好让父进程知道是那个子进程创建了,给子进程返回0代表子进程创建成功。
那我们就只能让子进程和父进程做一样的事情吗?当然不是我们可以通过分流让父子进程干不同的事情
1 #include
2 #include 3 using namespace std; 4 #include 5 #include 6 int main() 7 { 8 pid_t id =fork(); 9 if(id==0) 10 { 11 cout<<"I am a chidl process"< 运行结果:
总结:
下面是linux内核当中内核代码:
/* * The task state array is a strange "bitmap" of * reasons to sleep. Thus "running" is zero, and * you can test for combinations of others with * simple bit tests. */ static const char * const task_state_array[] = { "R (running)", /* 0 */ "S (sleeping)", /* 1 */ "D (disk sleep)", /* 2 */ "T (stopped)", /* 4 */ "t (tracing stop)", /* 8 */ "X (dead)", /* 16 */ "Z (zombie)", /* 32 */ };
下面一一解释:
R状态:R状态并不是指进程一直在CPU上运行而是在运行队列中,等待CPU调度。
下面我们用一段代码验证:
#include
2 #include 3 using namespace std; 4 #include 5 #include 6 int main() 7 { 8 while(1); 9 return 0; 10 } 运行这个程序:
S状态:(可中断睡眠,也叫浅度睡眠):
处于这个状态的进程因为在等某个事件的发生(如何等待信号量,和显示器就绪)而被挂起也就是该进程的task_struct 被放到对应事件的等待队列当中,当这些事件发生时被唤醒(进程从等待队列放到运行队列当中)现实情况中大部分进程处于等待队列中。
同样的我们用一段代码来验证这个状态:
1.#include
2 #include 3 using namespace std; 4 #include 5 #include 6 int main() 7 { 8 while(true) 9 { 10 cout<<"hello word"< 我们运行一下这个程序并于ps ajx指令来查看这个进程。
一开始这个程序是R状态过了一会就变成S状态,他不是一直在打印吗?为什么会变成S状态了呢?这是因为外设太慢了,一直在等外设就绪。
D状态:(不可中断状态也叫做深度睡眠)
在这个状态的进程通常会等待IO的结束,操作系统也无法杀死这个进程。举例:当一个进程在CPU上运行然后告诉磁盘说我有一些数据,磁盘你给我写,写完把结果告诉我。于是这个进程就在CPU上等待然后操作系统发现一个进程怎么在这啥都不干,于是操作系统说我要把你这个进干掉,当操作系统把这个进程干掉之后过了一段时间磁盘把数据写完了,要把结果告诉进程的时候发现这个进程没了,这时候磁盘就很难受不知道怎么半。为了防止这种情况的发生就出现了D状态,当操作系统发现一个进程为D状态,操作系统说好你进行在这里吧。(这种场景太难演示出来所以在这里就不演示了。
T状态(暂停状态)可以发送SIGSTOP让进程暂停。
同样的我们使用上面这份代码:
X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
Z状态(僵尸状态):
一个进程退出是不是就意味着,立马就进入僵尸状态呢?当然不是一个进程退出首先需要进入僵尸状态等待父进程来获取它的退出码等信息。这时候就进入了僵尸状态。这里就出现了僵尸进程:
1.僵尸状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲)没有读取到子进程退出的返回代码时就会产生僵(尸)进程。
2.僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态。下面我们通过一段程序来验证僵尸进程是否真的存在:
while :; do ps axj | head -1 && ps ajx | grep test^Csleep 2; echo "#############################################"; done
#include
2 #include 3 using namespace std; 4 #include 5 #include 6 int main() 7 { 8 pid_t id=fork(); 9 if(id==0) 10 { 11 while(1){ 12 printf("pid为:pid %d ppid为:%d",getpid(),getppid()); 13 sleep(1); 14 } 15 16 } 17 else{ 18 printf("I am father\n"); 19 sleep(70); 20 } 21 return 0; 22 } ~ 我们让子进程一直打印然父进程一直睡眠,在70秒之内把子进程干掉:
僵尸进程的危害:
进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!
维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护?是的!
那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!下面再来看一种进程孤儿进程:
1.父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?父进程先退出,子进程就称之为"孤儿进程”
2.孤儿进程被1号init进程领养,当然要有init进程回收喽。下面通过这段代码来演示:
1 #include
2 #include 3 using namespace std; 4 #include 5 #include 6 int main() 7 { 8 pid_t id=fork(); 9 if(id==0) 10 { 11 while(1){ 12 printf("pid为:pid %d ppid为:%d",getpid(),getppid()); 13 sleep(1); 14 } 15 16 } 17 else{ 18 sleep(10); 19 exit(11);//让父进程睡眠十秒之后就直接让父进程退出 20 21 } 22 return 0; 23 } 运行这段代码通过ps 命令查看这个进程:
我们发现这个进程被 1号进程给领养了。
可能有老铁会好奇那个有一些进程的后面会加一个+这是什么呢?这代码是在前台运行。不带代表在后台运行,如果我们想让它在后台运行我们只需要运行程序的后面加一个&即可。那两者有什么区别?
1.前台前程可以被ctr +c 中止我们在其上面输入其他指令无法执行(刷屏打印时)
2.后台程序不可以被ctr +c 中止我们在其上面输入其他指令可以执行(刷屏打印时)
总结: