举个例子:我们在和朋友使用qq聊天的时候,当我们在窗口输入了一句话,那么这句话就会从我们的输入设备(键盘)载入数据到内存中,我们点击发送的时候,cpu就会执行qq这个程序的发送的指令集,和内存中的那句话打交道,将他发送到服务器上,也就是我们的网卡。然后网卡再作为输入设备与我们的内存打交道,cpu执行服务器中的这句话的指令集,然后再通过内存发送到对面的电脑上,也就是磁盘上。
总结:
在学校校长不是直接管理学生,而是通过教师管理学生,校长只需要通过教师获取学生的数据(个人信息)即可,操作系统就是这样,不直接和管理对象直接接触,只需要获得他们的数据就行。
而对于这些数据,是通过struct结构体提取学生的关键词信息,比如age,height。对学生的数据进行链表式的管理。
创建结构体的过程是先描述学生的关键信息,使用链表将学生数据汇总是组织这些数据的过程。
操作系统也是如此。管理硬件的时候,对其中的数据先描述再组织。上面作为教师身份的是驱动程序,驱动会进行数据的采集和操作系统命令的执行。
总结:
操作系统就像银行一样,不能完全封装自己(银行窗口完全紧闭),不然客户无法存钱取钱与业务办理。因此银行既要保护自己,也要给上层用户提供服务。因此操作系统会提供部分接口直接访问操作系统,这就是系统接口,供上层开发者使用。
但是这种系统接口功能比较基础,对用户的要求也比较高,所以有心的开发者可以对部分系统调用进行封装,从而形成库,有了库,就有利于上层用户或者开发者进行更好的开发。
程序的本质:存储在磁盘上的文件。
进程的初步概念:程序执行的一个实例,正在执行的程序。
进程的初步内核概念:担当分配资源的实体。
当我们执行了很多程序的时候,形成进程,操作系统是直接对进程的可执行程序进行管理吗?不,还是先描述再组织,将进程的属性包装成struct结构体,在linux中他就叫tast_struct,存放着进程的属性,存在内存(RAM)中。再把这些数据的结构体组织成链表形式。也就是PCB。
操作系统对进程的管理也就变成了对PCB的管理,即为链表的增删查改。
/proc这是进程系统文件,比如要查看某个pid为1的进程,可以使用ll /proc/1
如果该进程在运行时候,可执行文件删除了,进程不会停止,这个系统带pid的系统目录会失效。
这可以查看某个进程的信息。(ps axj | grep ‘进程名’)
也可以用管道,ps axj|head -1 && ps axj |grep ‘进程名’,查看包括属性的显式
getpid和getppid可以获取进程的pid和ppid。
这是pid
这是pid和ppid,我们可以看到父进程是bash。
是用来创建子进程的。fork有2个返回值。
打印结果是2份,因此我们可以知道fork有2个返回值。
man fork看了一下,对于fork有2个返回值。返回成功时候,父进程返回子进程的pid,子进程返回0,返回失败时,父进程返回-1。
可以看到,父进程返回值ret是子进程的pid,子进程的返回值是0。
总结:
一:运行队列和运行状态
一个cpu对应一个运行队列,
进程执行的时候,需要先进入队列排队,让进程入运行队列,不是让可执行程序进入队列,而是让属性的集合task_struct 进入队列
进程在runqueue就是进入了R状态(运行状态),不是运行了才能是运行状态。
二:对于cpu和硬件速度的差异如何处理?
cpu的速度很快,外设的速度很慢,一个进程如果在运行队列中,需要访问硬件的时候,就要进行等待,意味着暂时用不到这个进程。进程不一定只占用cpu资源,还会访问外设,所以这如何解决?
操作系统会让这个进程暂时进入阻塞队列中去,让其他的需要cpu资源的进程进入运行队列中。当那个硬件访问完后需要cpu资源了再让那个进程重新进入运行队列。
三:阻塞有什么影响?
阻塞过多,内存不够用了,这时候操作系统便会让一些进程的代码和数据保存到磁盘上,这样一来节省了部分空间给别的进程使用,这样的状态叫做挂起状态,但是操作系统是公平的,当别的进程运行完毕,会把挂起状态的进程重新从磁盘中获取,重新进入运行队列。
同时将进程的代码和数据写入磁盘和从磁盘中拿出的过程也叫内存的唤入唤出。
/*
* 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 */
}
#include
int main()
{
while(1);
return 0;
}
R:进程要么在运行队列等待,要么在运行状态。
+:有+表示在进程在前台,用户可以使用ctrl+c终止进程,没有+为后台进程,不能用ctrl+c终止。
#include
int main()
{
while(1)
{
printf("我是谁\n");
}
return 0;
}
当使用printf的时候需要和外设显示器打交道,外设速度很慢,因此cpu在这段代码中的时间分配可能就是99%等待外设打印,1%执行算术运算。
S:浅睡眠状态,需要访问外设的时候,一般属于睡眠状态。意味着进程在等待事件完成。可以由操作系统杀掉进程。
D:深度睡眠状态,当我们高IO的时候就会可能发生,Linux有一条指令可以进入高IO(dd指令),可以试逝。
该状态下,进程只能断电情况下或者自己醒来从而中断D状态,无法由操作系统杀掉该进程。
#include
int main()
{
while(1)
{
printf("我是谁\n");
}
return 0;
}
让上面的代码从S进入T,可以使用kill -19 pid的指令,
我们使用kill -18 pid指令让程序继续进行,可以发现程序从T进入了R,而程序本来是R+,前面说过这是从前台变成了后台状态。
我尝试使用ctrl+c是杀不掉进程的
kill -19 pid后才能杀掉这个后台进程。
T:进程暂停,或者是让进程从前台进入后台状态。
t(tracing stop):当我们使用gdb进行调试进程的时候,也会进入暂停状态,这叫追踪暂停状态。
X:死亡状态。
当子进程退出的时候,等待父进程读取自己的数据和状态的时候,子进程就会处于僵尸状态。就好比生产过程中当工人完成任务了不能马上下班,上级需要获取工人的完成量和状态。
1 #include
2 #include
3 #include
4 int main()
5 {
6 pid_t id = fork();
7 if(id < 0)
8 {
9 perror("fork fail\n");
10 return 0;
11 }
E> 12 else if(fork > 0)
13 {
14 //father
15 printf("father is sleeping pid is %d",getpid());
16 sleep(30);
17 }
18 else
19 {
20 //child
21 printf("child is zombie pid is%d",getpid());
22 sleep(5);
23 return 0;
24 }
子进程先退出,父进程还没有读取子进程的代码的时候,子进程已经进入了Z的状态。
僵尸进程的危害:
如果僵尸进程一直存在,也就是父进程一直不读取子进程返回的代码,维护退出状态本身就要数据维护,也属于进程的基本信息,也就是struct_task,也就是说僵尸进程只要存在,PCB就要一直维护。如果一个父进程创建了很多子进程都不回收从而进入Z状态,就会造成内存资源的浪费,会造成内存泄露。
那我们可以手动杀掉这个进程吗?答案不行。
因为这个子进程已经退出,已经是死亡状态,无法手动杀掉。当进程被回收后(后续内容讲解),子进程立马变为X(dead)状态。
1 #include
2 #include
3 #include
4 int main()
5 {
6 pid_t id = fork();
7 if(id < 0)
8 {
9 perror("fork fail\n");
10 return 0;
11 }
E> 12 else if(fork > 0)
13 {
14 //father
15 printf("father is sleeping pid is %d",getpid());
16 sleep(30);
17 }
18 else
19 {
20 //child
21 printf("child is zombie pid is%d",getpid());
22 sleep(5);
24 }
杀掉父进程后,子进程被一号系统领养,如果不被领养,孤儿进程就无法回收了这样一来就内存泄露了。
使用ps -l会查看出进程的优先级,PRI就是优先级,NI可以手动更改叫nice,
优先级=系统默认优先级(80)+ NI值。
NI取值为(-20,19)。
用top命令更改NI值,进入top后按r,输入进程的pid,输入NI值。