进程深入学习笔记(1)

版权声明:本文为博主原创文章,欢迎转载,转载请声明出处! https://blog.csdn.net/hansionz/article/details/82870875

1.进程概念

进程指的是正在执行的程序或者程序的一个执行实例,站在内核的角度,进程担当分配系统资源(cpu时间、内存)的实体

2.描述进程(PCB)

(1)怎么管理进程?

操作系统是一款搞管理的软件,那么要怎么才能管理好进程呢?

  • 先把用结构体进程描述起来(进程控制块)
  • 在用一些数据结构将这些描述块组织起来

(2)描述进程

  • 所有的进程信息都放在进程控制块中(PCB)linux操作系统下的PCB叫做task_struct,它可以理解为进程属性的一个集合。
  • task_struct是linux内核中的一种数据结构(可以把它理解为结构体)。它会被加载到内存中并且包括描述进程的所有信息

(3)task_struct中的内容

下边是task_struct中的一小部分信息。实际上,描述一个进程需要很多的信息,这个结构体非常的大,有兴趣的朋友可以自行上网查一下task_struct中的所有信息。

标识符(PID):描述进程的唯一标识符,用来区别其他进程。
进程状态:进程的任务状态、退出代码、退出信号等。
优先级:相对于其他进程的执行次序,优先级高的进程应优先获得处理。
程序计数器:程序中即将被执行指令的下一条指令的地址。(cpu里一个重要的寄存器–PC指针)。
上下文数据:进程执行时处理器的寄存器中的数据。
I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
内存指针:程序代码和进程相关数据的指针(程序和数据的地址)和其他进程共享的内存块的指针。
资源清单:列出了进程在运行期间所需的全部资源(除CPU),还有一张已分配到该进程的资源的清单。
其他信息等等。

上下文数据是指程序执行时处理器各种寄存器中的数据。包括:

  • 通用寄存器。
  • 程序计数器:程序中即将执行的下一条指令的地址
  • 程序状态字PSW:含有状态信息,如执行方式中断屏蔽字等。
  • 用户栈指针:指每个用户进程都有一个或若干个与之相关的系统栈,用于存放过程和系统调用参数及调用地址

上下文数据存在的必要性:处理机处于执行状态时,正在处理的许多信息都是放在寄存器中。当进程被切换时,处理机状态信息都必须保存在相应的PCB中,以便在该进程重新执行时能再从断点继续执行。

3.组织进程

PCB是进程的唯一描述块,为了动态插入和删除,PCB由链表组织。进程创建时,为该进程生成一个PCB,进程终止时,回收PCB

4.查看进程信息

  • ls /proc/。要查看几号进程信息,就查看对应的文件。例如ls /proc/1
  • ps aux。例如ps aux | grep test | grep -v grep查看关于test的进程信息
  • top。相当于windows下的任务管理器

5.获取进程的标识符(PID)

我们可以直接通过系统调用接口获得进程的PID:它们在头文件<sys/type.h>和<unistd.h>中包括。

  • 子进程id:getpid()
  • 父进程id:getppid()

6.创建进程(fork)

新进程的创建,首先在内存中为新进程创建一个task_struct结构,然后将父进程的task_struct内容复制其中,再修改部分数据分配新的内核堆栈、新的PID、再将task_struct 这个PCB添加到链表中

(1)fork函数

fork函数的作用是从已经存在的进程中创建一个子进程。此时,新进程为子进程,源进程为父进程。fork函数位于头文件<unistd.h>中,它被定义为:

pid_t  fork(void);

fork函数的返回值:(fork函数有两个返回值)

  • 父进程中,fork返回新创建的子进程的PID
  • 子进程中,fork返回0
  • 当fork失败,返回-1(PCB是要占内存的,当内存不够或者实际用户进程超过了限制fork会调用失败)

为什么fork后,父进程要返回子进程的PID子进程要返回0呢?

  • 一个父进程多个子进程
  • 一个子进程一个父进程(fork之后父进程需要知道是哪一个子进程在完成任务)

(2)写时拷贝

fork之后,父子进程的代码和数据是共享的,但是当任意一个进程试图写入时(既修改数据),这样便会以写时拷贝的方式父子进程各自拥有一份数据副本。下图很清晰的反应了写时拷贝的场景。

在这里插入图片描述

为什么要有写时拷贝,而不是直接在创建PCB时拷贝一份呢?

我们可以站在以下角度思考:

  • 内存资源
  • 性能问题
    写时拷贝可以让我们更合理的使用内存空间,不直接在创建PCB时拷贝一份,可以避免拷贝时的一些系统开销

(3)fork的常规用法

  • 父进程希望子进程复制自己,执行不同的代码块,既用if else分流。
  1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<unistd.h>
  4 #include<stdlib.h>
  5 
  6 int main()
  7 {
  8     pid_t id=fork();
  9     int i=1;
 10     if(id<0){
 11         perror("use fork");
 12         exit(1);
 13     }
 14     else if(id==0){//child
 15         i--;
 16         printf("I am child ,pid is %d,My parent is %d , i is %d\n",getpid(),getppid(),i);
 17     }
 18     else{//parent
 19         i++;
 20         printf("I am perent,pid is %d,My parent is %d , i is %d\n",getpid(),getppid(),i);
 21         sleep(3);                                                                                                                 
 22     }
 23     return 0;
 24 }

运行结果如下:
在这里插入图片描述
一般来说,fork之后父、子进程执行顺序是不确定的,这取决于内核调度算法。进程之间实现同步需要进行进程通信。

  • 一个进程要执行一个不同的程序。fork返回后,调用exec函数(下边详解)

(4)vfork和fork的区别

  • vfork也是用于创建子进程。而vfork之后的父子进程共享地址空间,也就是说他们除了PCB不一样,在都一样,fork的子进程则具有独立的地址空间。
  • vfork的子进程保证让子进程先运行,在调用exec后父进程才可能被调度。
  1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<unistd.h>
  4 #include<stdlib.h>
  5 
  6 int num=10;
  7 int main()
  8 {
  9     pid_t id=vfork();                                                                                                             
 10     if(id<0){
 11         perror("use fork");
 12         exit(1);
 13     }
 14     else if(id==0){//child
 15         sleep(5);
 16         num=20;
 17         printf("child:num=%d\n",num);
 18         exit(0);
 19     }
 20     else{//parent
 21         printf("parent:num=%d\n",num);
 22     }
 23     return 0;
 24 }

运行结果如下:
在这里插入图片描述

运行结果是先等待5秒,然后先执行子进程,而子进程对数据修改为20,在父进程中num也输出为20,说明父子进程共享地址空间。

7.进程状态

(1)有哪些进程状态呢?

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

(2)修改进程状态

我们可以根据命令向相应的进程发送信号来改变进程的状态:

  • kill -l查看系统支持的信号列表
  • kill -SIGSTOP pid 使pid号进程停止(例如:kill -SIGSTOP 1234使1234号进程变为T状态)
  • kill -SIGCONT pid 使pid号进程继续运行

8.僵尸进程和孤儿进程

单独总结于我的另一边博客:
https://blog.csdn.net/hansionz/article/details/82829587

9.进程的优先级

cpu资源分配的先后顺序,就是指进程的优先级(priority)。优先级的进程有优先执行权利。

(1)查看系统的进程

命令ps -l用来查看系统进程
在这里插入图片描述

  • UID:执行者身份
  • PID:进程的PID
  • PPID:父进程的PID
  • PRI:进程的优先级,值越小优先级越高
  • NI:进程的nice值

(2)PRI和NI

  • PRI值越小就越快被执行,加入nice值后,PRI变为:PRI(new)=PRI(old)+nice
  • nice值为负值时,那么该程序的优先级值将变小,其优先级会变高,则会越快被执行。所以,调整进程优先级,在Linux下,就是调整进程nice值。
  • nice的取值范围为-20~19,一个40个级别。
  • 进程的nice值不是进程的优先级,而是进程优先级的修正数据

为什么调整优先级要存在上下限呢?
为了让调度器调度资源均衡

(3)调整进程优先级

  • 启动进程前调整:nice
    nice -n -10 ./test : 调整test进程的nice为-10

  • 调整已存在进程的nice:renice
    renice -10 -p 2189 :pid为2189的进程nice设为-10

  • top命令更改已存在进程的nice:
    top->进入top后按"r"->输入进程pid->输入nice值

猜你喜欢

转载自blog.csdn.net/hansionz/article/details/82870875