[linux] 进程相关概念理解

1. 什么是进程

在这里插入图片描述
假设在一个文件中写代码并生成一个可执行程序在磁盘中可执行程序本质也是一个二进制文件
文件 =内容+属性
内容即 自己写的代码和数据
属性即 创建时间、权限等信息

在这里插入图片描述

  • 使用 ./ 将其加载到内存中cpu访问代码和数据从而执行代码 把代码和数据放入内存中 就可以叫做进程么
  • 当然不是
    - 举例
    如何成为你的学校的学生呢
    只要想办法进入你的学校里在学校里就是你的学校的学生么
    当然不是看门的大爷和楼管阿姨也在学校里
    想要成为学生必须在学籍档案中有你个人的基本信息
    同理只把代码和数据放入内存中不叫作进程
    为什么基本信息在学籍档案中呢
    因为学校要对学生管理

在这里插入图片描述
随着程序加载到内存的数量增多操作系统就要考虑如何把加载的代码个数据进行管理
所以操作系统要管理进程
管理的本质是先描述在管理 (不懂的可以点击查看具体解释)

管理本质的解释

描述

在这里插入图片描述
使用结构体构建了结构体对象在操作系统教材中叫做 PCB 在Linux中叫做 task_struct
并且结构体提取了所有进程的属性
同样使用各自的结构体可以找到各自的代码和数据

组织

将结构体通过特定数据结构关联起来(以链表为例)
在这里插入图片描述
通过链表的增删查改操作来完成对进程的增加、删除、查找、修改

结论

进程是内核关于进程的相关数据结构+当前进程的代码和数据

2.查看进程

查看进程方法1

 #include<stdio.h>
  2 #include<unistd.h>
  3 int main()
  4 {
  5   while(1)
  6   {
  7     printf("hello world\n");
  8     sleep(1);                                                                                                                                                        
  9   }
 10   return 0;
 11 }


创建一个pro.c的文件同时生成一个可执行程序pro使之无线循环下去

创建终端

在这里插入图片描述

在第一个终端中点击右键复制SSH渠道就会自动生成终端2

输入命令显示进程

在保证终端1的pro程序运行时在第二个终端中
ps axj 查看当前系统中所有的进程
head -1 取第一行指令
grep pro 只查看自己的进程
grep -v grep 除了grep的内容显示出来

输入 ps axj | head -1 && ps axj | grep pro | grep -v grep即可查看当前pro可执行程序的进程

[yzq@VM-8-8-centos ~]$ ps axj | head -1 && ps axj | grep pro | grep -v grep
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
 3754  3943  3943  3754 pts/0     3943 S+    1002   0:00 ./pro

一个程序存在多个进程

首先创建三个终端
在这里插入图片描述

  • 在终端2和终端3中同时运行 ./pro 再次在终端1中使用指令ps axj | head -1 && ps axj | grep pro | grep -v grep发现生成两个PID值不同的进程
  • 将一个可执行程序多次加载内存可执行程序内部存在多个进程

查看进程方法2

ls /procproc 为process的简称保存进程相关属性的目录
在这里插入图片描述

  • 蓝色的数字就是进程的PID

查看成功

  • 在保证终端1正在运行./pro在终端2中以第一次生成的PID为例

在这里插入图片描述

  • PID值为3943ls proc/3943即可查看相关的进程属性

在这里插入图片描述

查看失败

  • 若将终端1的pro可执行程序关闭则进程不存在
[yzq@VM-8-8-centos ~]$ ls /proc/28439
ls: cannot access /proc/28439: No such file or directory

结论

  • 当把进程创建时proc目录下会自动创建以PID命名的目录里面会把内存运行的属性呈现出来

  • 当把进程终止时proc目录下会自动把PID命名的目录全部删除

3.通过系统调用获取进程标识符

1.获取PID值

  • getpid 需要头文件 <sys/types.h> 和<unistd.h>返回值为 getpid_t类型表示当前进程的PID值
#include<stdio.h>
  2 #include<sys/types.h>
  3 #include<unistd.h>
  4 int main()
  5 {
  6   while(1)
  7   {
  8     printf("我已经是一个进程了PID为%d\n",getpid());                                                                                                              
  9     sleep(1);                                                                                                                        
 10   }                                                                                                                                  
 11   return 0;                                                                                                                          
 12 }      
  • 在之前的pro.c文件进行修改将其内容修改为上面的并在终端1中使用./pro 执行可执行程序
[yzq@VM-8-8-centos lesson]$ ./pro
我已经是一个进程了PID为28286
我已经是一个进程了PID为28286
我已经是一个进程了PID为28286
我已经是一个进程了PID为28286
我已经是一个进程了PID为28286
我已经是一个进程了PID为28286
我已经是一个进程了PID为28286

  • 会生成不间断的相同PID值

验证PID值是否正确

  • 再次创建一个终端并命名为终端2并保证上述的pro程序在终端1中运行的情况下使用指令 ps axj | head -1 && ps axj | grep pro | grep -v grep发现PID值相同
[yzq@VM-8-8-centos lesson]$ ps axj | head -1 && ps axj | grep pro | grep -v grep
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
26652 28286 28286 26652 pts/0    28286 S+    1002   0:00 ./pro

2. 获取父进程PID值

getppid 头文件与getpid相同返回值为父进程的PID值

 1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<unistd.h>
  4 int main()
  5 {
  6   while(1)
  7   {
  8     printf("我已经是一个进程了PID为%d,我的父进程PID为:%d\n",getpid(),getppid());                                                                                 
  9     sleep(1);                                                                                                                                                    
 10   }                                                                                                                                                              
 11   return 0;                                                                                                                                                      
 12 }  
  • 再次将终端1中的pro.c文件内容修改为上面
[yzq@VM-8-8-centos lesson]$ ./pro
我已经是一个进程了PID为1013,我的父进程PID为:32452
我已经是一个进程了PID为1013,我的父进程PID为:32452
我已经是一个进程了PID为1013,我的父进程PID为:32452
我已经是一个进程了PID为1013,我的父进程PID为:32452

  • 在终端1中输入./pro显示当前进程PID为 1013父进程PID为 32452

验证

  • 在确保终端1中的pro可执行程序正在运行打开终端2 输入ps axj | head -1 && ps axj | grep pro | grep -v grep 指令
[yzq@VM-8-8-centos lesson]$ ps axj | head -1 && ps axj | grep pro | grep -v grep 
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
32452  1013  1013 32452 pts/2     1013 S+    1002   0:00 ./pro

  • 说明使用getppid查询结果正确

3. 父进程为什么不变化

[yzq@VM-8-8-centos lesson]$ ./pro
我已经是一个进程了PID为2050,我的父进程PID为:32452
^C
[yzq@VM-8-8-centos lesson]$ ./pro
我已经是一个进程了PID为2059,我的父进程PID为:32452
^C
[yzq@VM-8-8-centos lesson]$ ./pro
我已经是一个进程了PID为2065,我的父进程PID为:32452
^C

  • 在终端1中多次运行./pro发现当前进程PID一直在变而父进程的PID没变过
  • 父进程的PID为32452在终端2中输入 ps ajx | head -1 && ps ajx |grep 32452 指令
[yzq@VM-8-8-centos lesson]$ ps ajx | head -1 && ps ajx |grep 32452
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
  907  3167  3166   907 pts/3     3166 R+    1002   0:00 grep --color=auto 32452
32451 32452 32452 32452 pts/2    32452 Ss+   1002   0:00 -bash


  • 说明父进程PID 为 -bash

  • bash为命令行解释器本质上也是一个进程
    命令行启动的所有程序最终都会变成进程而该进程对应的父进程都是bash

4. 为什么都是bash

bash怕你写的代码有问题所以使用bash创建的子进程完成任务这样就算是挂了bash也没事

4.指定进程暂停

  • 在终端1中运行./pro在终端2中输入 kill - 9+自己进程的PID
[yzq@VM-8-8-centos lesson]$ ./pro
我已经是一个进程了PID为29031,我的父进程PID为:28428
我已经是一个进程了PID为29031,我的父进程PID为:28428
我已经是一个进程了PID为29031,我的父进程PID为:28428
我已经是一个进程了PID为29031,我的父进程PID为:28428
我已经是一个进程了PID为29031,我的父进程PID为:28428
我已经是一个进程了PID为29031,我的父进程PID为:28428
我已经是一个进程了PID为29031,我的父进程PID为:28428
我已经是一个进程了PID为29031,我的父进程PID为:28428
我已经是一个进程了PID为29031,我的父进程PID为:28428
我已经是一个进程了PID为29031,我的父进程PID为:28428
我已经是一个进程了PID为29031,我的父进程PID为:28428
我已经是一个进程了PID为29031,我的父进程PID为:28428
我已经是一个进程了PID为29031,我的父进程PID为:28428
我已经是一个进程了PID为29031,我的父进程PID为:28428
Killed

  • 在终端2中输入 kill - 9 29031即可在终端1中显示killed表示结束

5.如何创建子进程

创建子进程—— fork头文件为<unistd.h> 返回值是 pid_t类型

#include<stdio.h>
  2 #include<sys/types.h>
  3 #include<unistd.h>
  4 int main()
  5 {
  6   printf("AAAA\n");
  7   fork();
  8   printf("BBBB\n");
  9  sleep(1);                                                                                                                                                           
 10   return 0;                                                 
 11 } 
  • 继续在终端1中修改pro.c文件中的内容如上
[yzq@VM-8-8-centos lesson]$ ./pro
AAAA
BBBB
BBBB

  • 运行pro可执行程序发现竟然执行两次BBBB
    这是为什么呢我们继续往下看
#include<stdio.h>
  2 #include<sys/types.h>
  3 #include<unistd.h>
  4 int main()
  5 {
  6   printf("AAAA\n");
  7   fork();
  8   printf("BBBB:pid:%d,ppid:%d\n",getpid(),getppid());                                                                                                                
  9  sleep(1);                                                                                                                                                          
 10   return 0;                                                                                                                                                         
 11 }                                                                                                                                                                   
       
  • 修改por.c文件的内容加上自己和父进程的PID值
[yzq@VM-8-8-centos lesson]$ ./pro
AAAA
BBBB:pid:4285,ppid:31919
BBBB:pid:4286,ppid:4285

  • 终端1中./pro运行可执行程序两个执行B的printf语句打印自己进程的PID值不同说明是两个进程
  • 而下面BBBB的父进程PID与上面BBBB的子进程PID相同说明创建了子进程

1. fork返回值

  • 父进程返回子进程的PID值子进程返回0失败返回-1
  1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<unistd.h>
  4 int main()
  5 {
  6   printf("AAAA\n");
  7  pid_t ret= fork();
  8   printf("BBBB:pid:%d,ppid:%d,%d,%p\n",getpid(),getppid(),ret,&ret);
  9  sleep(1);                                                 
 10   return 0;                                                
 11 } 
  • 修改pro.c文件内容加上ret的值和地址
[yzq@VM-8-8-centos lesson]$ ./pro
AAAA
BBBB:pid:7799,ppid:31919,7800,0x7ffefc72c02c
BBBB:pid:7800,ppid:7799,0,0x7ffefc72c02c

在终端1中运行./pro上面的BBBBret值返回是下面BBBB的PID值 说明是父进程
而下面的BBBBret值为0,说明是子进程

2.使父子进程执行不同的任务

#include<stdio.h>
  2 #include<sys/types.h>
  3 #include<unistd.h>
  4 int main()
  5 {
  6  pid_t ret= fork();
  7  if(ret==0)
  8  {
  9    //子进程
 10    while(1)  
 11    {
 12    printf("我是子进程我的pid是:%d,我的父进程是:%d\n",getpid(),getppid());
 13    sleep(1);
 14    }
 15     
 16  }
 17  else if(ret>0)
 18  {
 19    //父进程
 20    while(1)
 21    {
 22    printf("我是父进程我的pid是:%d,我的父进程是:%d\n",getpid(),getppid());
 23    sleep(1);
 24    }                                                                                                                                                                 
 25  }
 26  else
 27  {   
 //报错
 29  }         
 30   return 0;
 }

  • 修改pro.c文件的内容设置if else语句实现
[yzq@VM-8-8-centos lesson]$ ./pro
我是父进程我的pid是:13505,我的父进程是:31919
我是子进程我的pid是:13506,我的父进程是:13505
我是子进程我的pid是:13506,我的父进程是:13505
我是父进程我的pid是:13505,我的父进程是:31919
我是子进程我的pid是:13506,我的父进程是:13505
我是父进程我的pid是:13505,我的父进程是:31919
我是父进程我的pid是:13505,我的父进程是:31919
我是子进程我的pid是:13506,我的父进程是:13505

父进程和子进程是同时运行的
说明在多执行流的环境下 if和else if可以同时成立

3. 结论

  • fork之后执行流会变成2个
  • fork之后谁先运行由调度器决定
  • fork之后fork之后的代码共享通常通过if和else if来进行执行流分流

6. fork 原理

1.fork做了什么

在这里插入图片描述

子进程pcb的大部分属性会以父进程pcb为模板把父进程大部分里面的数据拷给子进程
小部分属于子进程私有的例如PID、PPID值
因为进程等于数据结构+代码和数据所以父进程指向自己的代码和数据子进程也会指向同样的代码和数据
创建子进程创建独立的pcb结构父子进程看到的是同一份代码和数据

2.fork 如何看待代码和数据

在这里插入图片描述

当我们把画图关闭后并不会影响有道云笔记的使用说明他们都是独立存在的
进程在运行的时候是具有独立性的
当我们在执行代码同时运行父子进程时若使用 kill- 9 干掉父进程后子进程仍能运行
父子进程在运行时也是具有独立性的

父子进程指向同一块代码和数据独立性如何保证

代码
代码在内存区域是只读的(从来不会自己发生变化,不会有人修改)
父子进程两者都读不会互相影响

数据

  1 #include<stdio.h>  
  2 #include<sys/types.h>  
  3 #include<unistd.h>  
  4 int main()  
  5 {  
  6   int x=100;  
  7  pid_t ret= fork();  
  8  if(ret==0)  
  9  {  
 10    //子进程  
 11    while(1)  
 12    {
 13    printf("我是子进程我的pid是:%d,我的父进程是:%d,%d\n",getpid(),getppid(),x);
 14    sleep(1);
 15    }
 16     
 17  }
 18  else if(ret>0)
 19  {
 20    //父进程
 21    while(1)
 22    {
 23    printf("我是父进程我的pid是:%d,我的父进程是:%d,%d\n",getpid(),getppid(),x);
 24    x=50;
 25    sleep(1);
 26    }
 27  }
 28   return 0;
 29 }   
  • 在终端1中修改pro.c文件的内容
[yzq@VM-8-8-centos lesson]$ ./pro
我是父进程我的pid是:26332,我的父进程是:21231,100
我是子进程我的pid是:26333,我的父进程是:26332,100
我是父进程我的pid是:26332,我的父进程是:21231,50
我是子进程我的pid是:26333,我的父进程是:26332,100
我是父进程我的pid是:26332,我的父进程是:21231,50
我是子进程我的pid是:26333,我的父进程是:26332,100
我是父进程我的pid是:26332,我的父进程是:21231,50
我是子进程我的pid是:26333,我的父进程是:26332,100

使用./pro执行可执行程序修改父进程中的x值后只有父进程的x值被修改子进程x值不变
说明如果有一个进程把数据改了并不会影响另一个进程
当有一个执行流尝试修改数据的时候操作系统自动给当前进程触发写时拷贝
4

3.fork如何理解两个返回值问题

在这里插入图片描述

  • 当我们函数内部准备执行return的时候我们的主体功能已经完成
  • fork本质上是操作系统提供的一个创建子进程的函数
  • 所以当到return时说明创建子进程已经完成了return语句父进程会执行一次子进程执行一次共执行两次
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: linux