进程概念

基本概念:

  • 进程是程序的一个执行实例;
  • 从内核来看,进程是担当分配系统资源的实体。        

注:在Linux操作系统中,大多数指令都是创建了一个个的进程。


操做系统如何管理内存?

答: 使用一个结构体(PCB)来描述进程;

       使用高效的数据结构来组织进程。


描述进程--PCB(process control block):

    进程的信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。这个数据结构在Linux操作系统中被叫做task_struct。

    存放进task_struct结构体中的信息有:

  • 标识符:用来区别其他进程的唯一标识符;
  • 状态:任务的状态,退出代码,退出信号等;
  • 优先级:相对于其它进程的优先级;
  • 内存指针:是用来存放代码和数据的地址,还有其他进程共享的内存块的指针;
  • 程序计数器:程序中即将被执行的下一条指令的地址;
  • 上下文:进程执行时寄存器中的数据(保护现场,进程切换后,切换回来的时候方便恢复到切换时的进程执行的状态);
  • I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表;
  • 记账信息:可能包括处理器的时间总和,使用的时钟数总和,时间限制和记账号等。
  • 其它信息。

组织进程:

    使用一个代替头结点的带环的双向链表来组织进程。

    创建一个进程的本质是:创建一个task_struct结构体,并将该结构体进行初始化插入上面说的带头结点的双向带环链表。

查看进程:

  • 可以通过系统文件夹(/proc)查看


  • 若要查看pid为1的进程,需要查看/proc/1文件夹


  • 大多数进程可以使用top、ps等工具获取

ps aux(查看所有进程)


ps aux | grep hello (用ps aux的输出来作为grep的输入)

  int main()                                                                                                                         
  {
      while(1)
      {
          sleep(1);
      }   
      return 0;
  }   

通过系统调用获取进程标识符(PID):

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>                                                                                                               
int main()   
{            
    pid_t pid = getpid();        //获取当前进程标识符
    pid_t ppid = getppid();      //获取当前进程的父进程的标识符
    printf("pid:%d ppid:%d\n", pid, ppid);
}   

通过系统调用创建进程--fork:

通过fork系统调用可以创建一个子进程,通过在Linux操作系统上使用man fork学习fork,我们可以总结出以下结论:

  • fork创建进程以原进程为模板,复制PCB的内容,拷贝代码和数据(共用一份代码,数据各自开辟空间,私有一份。采用写时拷贝)。


  • 调用一次fork有两个返回值,在父进程和子进程中分别返回;父进程中返回子进程的PID,子进程中返回0.

编译运行下面的代码,可以看到父进程的中的返回值恰好是子进程的PID:

int main()
{
    pid_t ret = fork();
    if(ret > 0)
    {
        printf("father:%d   ret:%d\n", getpid(), ret);//father
        sleep(1);
    }
    else if(ret == 0)
    {
        printf("child:%d   ret:%d\n", getpid(), ret);//child
    }
    else
    {                                                                                                                                
        perror("fork\n");
    }   
}

  • 父子进程的执行顺序不一定,取决于操作系统调度器;
  • 父子进程都从fork执行结束的位置开始执行;
  • fork失败的原因主要有:内存不足和创建的进程达到数目上限。

进程状态:

R(running):运行(就绪状态),不一定正在运行,也可能是在运行队列中;

S(sleeping):睡眠状态,意味着进程正在等待事件完成,在这个状态是可以打断的;


D(disk sleeping):磁盘休眠状态,也叫不可中断睡眠状态,该状态通常会等待IO的结束;(当密集的读写IO设备时会出现这个状态)

T(stopping):停止状态,可以通过SIGSTOP信号来停止进程,通过SIGCONT信号来让进程继续运行;

下面一段代码可以使进程一直处于运行状态,

#include <stdio.h>                                                                                                                   
int main()
{
    while(1)
    {   
        ;   
    }   
    return 0;
}

然后我们使用信号来使进程停止:


也可以使用信号来恢复:


t(tracing stop):跟踪状态;

X(dead):理论上存在;

Z(zombie):僵死状态。

僵尸进程:

    僵死状态是一个特殊的状态,当子进程退出而父进程未能读取子进程的返回值,就会产生僵死状态(进程)。这时候僵尸进程会一直等待父进程读取返回代码。

    所以,产生僵尸进程的条件是子进程退出,父进程还在运行同时未读取子进程的返回状态,子进程进入僵尸状态。

创建一个僵尸进程:

int main()
{      
    pid_t ret = fork();
    if(ret > 0)
    {
        while(1)
        {
            printf("father:%d   ret:%d\n", getpid(), ret);//father
            sleep(1);
        }
    }
    else if(ret == 0)
    {
        printf("child:%d   ret:%d\n", getpid(), ret);//child                                                                         
        sleep(1);
    }
    else
    {
        perror("fork\n");
    }
}           

僵尸进程的危害:

1 进程的退出状态必须被维持下去,因为他要告诉父进程,你交给我的任务完成的怎么样了。如果父进程一直不读取,子进程就会一直处于Z状态。

2 维护进程的退出状态要用数据维护,也是进程的基本信息,保存在(task_struct)PCB中;也就是说,如果Z状态一直不退出,PCB就要一直维护。

3  如果一个父进程建立了许多子进程而不进行回收,就会造成内存资源的浪费。因为保存进程信息需要结构体PCB,定义结构体需要在内存中开辟空间,又不进行回收,就会造成内存资源的浪费,造成内存泄漏

孤儿进程:

如果父进程提前退出,那么这时候子进程就会成为孤儿进程,孤儿进程会被1号进程领养。

下面来模拟实现一个孤儿进程:(3秒后父进程退出,子进程被1号进程回收)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
    pid_t ret = fork();                                                                                                              
    if(ret > 0)
    {   
        //father
        printf("father:pid %d  ppid:%d  ret:%d\n", getpid(), getppid(), ret);
        sleep(3);
    }   
    else if(ret == 0)                                                                                                                
    {
        //child
        while(1)
        {
            printf("child: pid %d  ppid:%d   ret:%d\n", getpid(), getppid(), ret);
            sleep(1);
        }
    }
    else
        perror("fork");
    return 0;
}

值得注意的是,孤儿进程不是状态,而僵尸进程是一个状态。

进程优先级:

基本概念:

  • CPU资源分配的先后顺序,就是指进程的优先权(priority)
  • 优先权高的进程有优先执行权利。配置进程优先级对任务环境的Linux很有用,可以改善系统性能。
  • 还可以把进程运行到指定的CPU上,这样,把不重要的进程安排到某个CPU,可以大大改善系统性能。

查看优先级:

ps -l


ps -al

ps -elf

ps elf

PRI是进程的优先级,数值越小,优先级别越高。

NI是nice值,表示进程可被执行的优先级的修正数值。

PIR(new)=PIR(old)+nice值,当nice值为正时,优先值越大,优先级别越小;当nice值为负时,优先值越小,优先级别越大,越快被执行。

NI的取值范围是-20~19,共40个数据。

修改进程的优先级:

可以使用nice和renice命令修改进程优先级。

启动进程前调整:nice -n -5 ./hello

调整已经存在的进程:renice -5 -p 3498      //将PID为3498的进程的nice值改为-5

 用top查看进程优先级并修改进程优先级:

输入top回车->进入top后按“r”->输入进程PID->输入nice值


注:

  • 进程数目众多,而CPU只有少量甚至只有1个,所以为了高效完成任务,进程具有竞争性,它们要合理竞争相关资源,这样就有了优先级;
  • 多进程运行,需要独享各自的资源,做到进程运行期间互不干扰,进程具有独立性
  • 多个进程在不同的CPU下同时运行,叫做并行
  • 多个进程在一段时间内在同一CPU下用切换的方式共同推进,由于这个时间非常短,人一般察觉不到,就像进程是同时发生的一样,叫做并发

猜你喜欢

转载自blog.csdn.net/weixin_40417029/article/details/79995125