linux篇linux进程(上)

Posted 东条希尔薇

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux篇linux进程(上)相关的知识,希望对你有一定的参考价值。

作者介绍:

关于作者:东条希尔薇,一名喜欢编程的在校大学生
主攻方向:c++和linux
码云主页点我
作者CSDN主页地址


目录

认识操作系统

感性的认识操作系统

我们的计算机,其实它的本质是由冯诺依曼结构为基础,由一个个硬件之间相互配合而形成的拥有计算功能的机器罢了

而我们是不能直接对一堆冷冰冰的机器进行交互的。它需要有一个管家,来管理好这些硬件,并且这个管家我们是能够直接使用的,这个管家,就叫做操作系统

像这种任何计算机包含的一种最基本的能够进行软硬交互的一款软件,我们把它叫做操作系统(英文缩写OS)

而狭义上的OS,它包括:

  • 内核:进程管理,内存管理,文件管理,驱动管理:这是OS用于管理硬件的
  • 其它程序:函数库,shell程序等,这些程序能帮助用户更方便的使用OS

理性的认识操作系统

既然我们知道了操作系统是一款搞管理的软件,那么它是如何搞管理的呢?

首先,我们先来认识一下OS的工作基本原理

先上一张OS的层次图


通过这张图:我们可以得出以下结论:

OS对下先通过调用驱动程序(驱动程序是能直接与硬件交互的一种软件),能往下操作硬件

对应用户,OS有一些暴露在外的用户能够直接调用的接口(其实说白了,这个接口就是函数,就是对一些接口进行封装而成的),能使用户向OS下达命令,实现一些功能

补充:OS是不信任任何用户的,所以我们也只能通过接口来与OS打交道

所以,在整个计算机设计架构中,OS的定位是:一款纯正的“搞管理”的软件

初步认识OS的管理

先看看生活中,我们有哪些管理的例子?

我们可以拿校长和学生来举例子,可以得出管理者和被管理者的关系

我们可以想象在学校中,你可能很少与你的校长打交道,而你在学校需要遵守什么规则,你的校级评奖等等,都是需要通过校长的
假如,你要给一个学生发奖,需要通过哪些流程?先查出他的考试成绩,然后可能需要开会来决定你最后给这位学生颁奖,最后通过某位老师,把奖状发下去

所以,我们可以得出以下的结论:

  • 管理者和被管理者并不直接打交道
  • 如何对被管理者进行管理?先进行一些决策,再把这些决策施行

但是我们可不能盲目的管理,比如你肯定不会给全年级倒数第一发奖学金。所以,我们如果要管理,可能还需要一些材料来支撑我们高效的管理

这个材料,就是数据

比如,你要查看一个学生的基本信息,可能需要查看他的生源地啊,成绩啊,获奖历史啊等等数据,然后根据这些数据来决定是否给他发奖学金

那么,如何聚合这些数据呢?

而在c++中,我们可以使用来描述我们需要管理的一个个对象,c语言中我们叫结构体

而这个过程,我们通常叫做数据的描述

其次,如果你要快速找出全校排满前几的学生,可能需要一些数据结构和算法,来高效的找出你需要的对象

而这个过程,我们通常叫做数据的组织

所以,我们可以得出一个十分重要的结论

计算机执行管理时,先把管理对象描述起来,再把管理对象组织起来

  • 描述在系统底层使用struct结构体
  • 组织可以使用链表,队列等数据结构进行组织

进程的认识

进程管理作为OS一款十分重要的组件之一,它可以管理我们计算机中运行的所有程序

而进程又是什么呢?

这是书本上的定义:

程序的一个执行实例,正在运行的程序,加载到内存中的程序

而根本我们本文提到的OS管理的中心思想,我想重新给进程下一个定义:

进程=程序对应的文件内容(struct)+相关数据结构(比如优先级队列)

OS如何管理进程?先描述再组织

其实OS中的描述也是这样,在每个进程开始时,都会创建对应的PCB进程控制块

PCB的本质就是task_struct结构体,进程的所有属性都存储到了这个结构体中

所有,有了PCB,所有的进程管理都与程序本身无关,而与PCB强相关

PCB包含的所有信息:

  • 标示符: 描述本进程的唯一标示符,用来区别其他进程。
  • 状态: 任务状态,退出代码,退出信号等。
  • 优先级: 相对于其他进程的优先级。
  • 程序计数器: 程序中即将被执行的下一条指令的地址。
  • 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
  • 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
  • I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
  • 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
  • 其他信息

不急,下文将会详细地说明它们分别的含义

进行管理图示:

PCB的基本数据说明

查看进程

linux下,我们可以使用ps命令来查看进程

有两种指令:

ps axj | grep “你的程序名”
ps -l查看运行中进程的详细信息
因为linux下一切皆文件,所以进程的详细信息都被保存在一个proc文件夹中

演示:

我们先写出一个死循环,一直运行的程序

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
   
int main()

   while(1)
    
      printf("hello,pid:%d,ppid:%d\\n",getpid(),getppid());                                                         
      sleep(1);
    
    return 0;

注:unistd.h用于调用sleep休眠函数
sys/types.h用于调用pid(后文讲解)

我们把这个程序取名为test,使其开始运行

然后打开新的对话框,查看


或者,利用文件查找

PCB基本数据之标识符

每个进程都有一个独一无二的标识符,就像人的名字一样,用来区分不同的进程

在程序中,我们使用getpid函数来获取当前进程的pid,使用getppid来获取它的父进程(后文讲解)的id

PCB基本数据之上下文数据

我们知道,一个电脑只有1个cpu,而我们的电脑可能会同时存在几十个进程,它们真的是同时运行的吗

其实不是的,OS规定了每个程序单次运行的时间片,也就是每个进程只能运行这么多时间,然后立刻切换到下一个进程,但是由于进程之间的切换极快,(大概是纳秒ns级别),所以看起来像在一起运行一样,这种模式我们也称作并发

而上下文数据,就是保存每次进程在运行时生成的临时数据,可以实现进程切换回来时,立刻恢复到切换前的状态继续运行此程序

利用fork来认识父子进程

我们知道,我们每个进程都有自己的父进程,父进程负责回收管理其对应的子进程,而有些进程的父进程可以直接是OS本身(pid为1)

而我们可以查看我们上面程序的父进程的信息



它的父进程是bash(bash是shell外壳程序的一种),其实,基本所有命令行程序的父进程都是bash

fork是一种可以创建子进程的函数,它的返回值是一个int

用fork接受返回值,可能会出现意料之外的情况

测试代码:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
   
   int main()
   
     int ret=fork();
   
    if(ret==0)
    
      printf("hello world!\\n");
    
    else
    
      printf("can you see me?\\n");
    
    sleep(1);                                                                                                      
    return 0;
  

一般我们的函数只有一个返回值,但是我们却发现我们的程序却两个条件都执行了!我们在语言层次上根本不能理解这种行为


其实,正是因为我们的程序创建了一个子进行,它们在fork处进行的分流,分别执行他们各自的语句罢了,而它们接受到的返回值分别是不一样的

我们可以验证一下:

  int main()
   
     int ret=fork();
  
     if(ret==0)
    
     while(1)
     
        printf("hello world!,pid:%d,ppid:%d\\n",getpid(),getppid());
        sleep(1);
      
    
    else
    
      while(1)
      
       printf("can you see me?,pid:%d,ppid%d\\n",getpid(),getppid());
        sleep(2);
      
                                                                                                                  
    return 0;
  

我们发现,helloworld的父进程刚好就是can you see me

那么,父子进程有什么相互联系呢?

  • 父子进程之间的数据是共享的,但它们又不完全共用一个空间,而是会发生写时拷贝,简单验证:
   #include<stdio.h>
   #include<unistd.h>
   #include<sys/types.h>
   //两个进程都打印创建进程之前的数据
   int main()
   
     int a=10;
     int ret=fork();
   
    if(ret==0)
    
      printf("%d\\n",a);
      sleep(1);
    
    else
    
      printf("%d\\n",a);
      sleep(1);                                                                                                    
    
    return 0;
  


虽然共享同一数据,但父子进程之间的数据不会直接影响的验证

   #include<stdio.h>
   #include<unistd.h>
   #include<sys/types.h>
   //两个进程都打印创建进程之前的数据
   int main()
   
     int a=10;
     int ret=fork();
   
    if(ret==0)
    
      a=20;//修改a
      printf("%d\\n",a);
      sleep(1);
    
    else
    
      printf("%d\\n",a);
      sleep(1);                                                                                                    
    
    return 0;
  

  • fork的返回值问题:如果子进程创建失败,给父进程返回-1。如果创建成功,给父进程返回子进程pid,给子进程返回0

  • 为什么要创建子进程?多个进程运行一个代码,提高效率

  • fork子进程后,一般要用ifelse分流,让它们分别执行不同的功能

PCB基本数据之进程状态

进程状态可以方便OS快速判断进程的状态,方便使其完成各种功能,本质是一种分类

linux中的进程一般有以下状态:

  • R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列
    里。
  • S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠
    (interruptible sleep))。
  • D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的
    进程通常会等待IO的结束。
  • T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可
    以通过发送 SIGCONT 信号让进程继续运行。
  • X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态

回到我们的第一个程序,状态码在这里显示:

睡眠状态和运行状态

  • 运行状态:其实一个程序在运行状态,并不代表它一定在运行,它也可能在cpu的等待队列中,在时间片之外罢了,也就是在并发中罢了
  • 睡眠状态:如果一个程序需要的资源没有准备好,那么程序将会睡眠,等待资源准备就绪了它才会再次运行

演示:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
   
int main()

   while(1)
    
      printf("hello,pid:%d,ppid:%d\\n",getpid(),getppid());                                                         
      //sleep(1);//我们删除它的休眠
    
    return 0;

程序打印的这么快,为什么它仍然在休眠状态呢?

因为我们的IO设备的相应其实相对于cpu很慢(cpu在纳秒级而io在毫秒级)
所以cpu相应完后io设备却还没准备就绪,所以程序在进行休眠

但相对我们人来说,cpu的切换实在太快,所以我们可能会认为程序一直在运行,其实它本来大部分时间在休眠

官方定义:

把程序从等待队列放在运行队列中,就叫唤醒

等待队列中的程序处于睡眠态
运行队列中的程序处于运行态

特殊状态之僵尸状态

当一个进程需要退出时,系统会回收这个进程的资源,然后进程就进入了死亡

而linux却不能随便让一个程序死亡,它需要先确认退出原因,确认后才会允许死亡

而一个进程在发出死亡请求后,linux确认死亡原因会有一个时间段,这个进程在这个时间段就会被置为僵尸状态

验证:根据子进程的维护由父进程管理,所以我们先让子进程死亡,然后不让父进程正常退出

先让这个程序运行

   int main()
   
   
     int ret=fork();
   
    if(ret==0)
    
      while(1)
      
        printf("hello world!,pid:%d,ppid:%d\\n",getpid(),getppid());
        sleep(1);
      
    
    else
   
      while(1)
      
        printf("can you see me?,pid:%d,ppid%d\\n",getpid(),getppid());
        sleep(2);
      
    
    return 0;                                                                                                      
  

然后杀掉子进程


然后这个子进程就会进入z状态(僵尸状态)

根本原因是父进程在运行是无法读取子进程的状态,所以无法回收子进程

僵尸进程我们需要尽量避免,因为会导致过度占用空间和内存泄漏

特殊进程之孤儿进程

父子进程在同时运行时,父进程先退出,而子进程会因为没有父进程而被置为孤儿进程

孤儿进程将会被1号进程(OS本身)领养

PCB基本数据之优先级

为什么要有优先级?本质是因为资源太少而进程太多

所以OS会把优先级高的进程赋予其优先使用资源的权限,这样优先级可以把重要的和不重要的进程分开,以提高OS的效率

如何查看?
中间的PRI和NI就是跟优先级有关的


PRI代表进行的优先级,该值直接决定一个程序的优先等级

PRI值默认为80,值越小,说明其优先级越高

NI是优先级的校正值,可以人为的修改,PRI的最终值也取决于NI值(PRI=80+NI)

如何修改优先级?

输入top后按r->输入进程pid->输入nice

强烈建议大家不要随意修改进程优先级,会导致进程饥饿问题

以上是关于linux篇linux进程(上)的主要内容,如果未能解决你的问题,请参考以下文章

linux篇linux进程(上)

linux篇linux进程(上)

Lab3—结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程

linux性能评估-cpu案例操作篇

结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程

结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程