【进程概念】启动进程 | 查看进程 | 创建进程

目录

启动进程

查看进程

方法1:/proc

方法2:查看脚本

​方法3:系统调用获取进程标示符❗❗

终止进程 

创建进程(主fork)

🙂查看父子进程的pid 

🙂进程创建/执行/终止

🙂多次重新启动进程查看pid和ppid

​🙂fork创建子进程 

🙂fork的返回值

🙂why❓fork有两个返回值

进程的当前工作路径 


从本篇开始陆续介绍Linux中进程的task_struct内部的属性。 

启动进程

  • ./ 本质上就是让系统创建进程并运行。
  • myprocess.c是C语言源代码
  • myprocess是可执行文件(gcc/g++)
  • 可执行的程序的可执行文件在磁盘上
  • ./运行程序:把可执行程序加载到内存上,OS创建task_struct,进入排队,等待调度。
  • 启动程序:就是让系统创建进程并运行。
  • 我们自己写的代码形成的可执行程序 == 系统指令(都是可执行程序) == 可执行文件。
  • 在Linux中运行的大部分指令操作,本质都是运行程序。
  • 在Linux中启动进程一般make(shell命令行)
  • 在windows启动进程双击(图形化界面)

【Makefile】 

  1 myprocess:myprocess.c
  2     gcc -o $@ $^ -g
  3 .PHONY:clean
  4 clean:
  5     rm -f myprocess 

【myprocess.c】 

  1 #include<stdio.h>
  2 #include<unistd.h>//sleep函数包含的头文件
  3 int main()
  4 {
  5     //让这个进程持续运行不停止
  6     while(1)
  7     {
  8         printf("I am a process!\n");
  9         sleep(1);
 10     }
 11     return 0;                                                                                                
 12 }
 ./myprocess

I am a process!
I am a process!
I am a process!
I am a process!
//之后myprocess已经是一个进程

【在/usr/bin/find目录下查看的可执行程序】

file /usr/bin/find

查看进程

 怎么查看进程呢?父进程,子进程?

  • 进程的信息可以通过 /proc 系统文件夹查看
  • 系统调用获取进程的标识符
  • 查看脚本

方法1:/proc

如:要获取PID为1的进程信息,你需要查看 /proc/1 这个文件夹。

/pro就是实时的把内存的进程信息挂接到文件当中,以文件的方式可以查看进程信息。

/pro就是/process

每启动一个进程,在/pro目录下,产生一个以该进程pid命名的文件夹(目录),该目录保存进程的属性信息,链接信息等等。

  • 查看 ls /proc/1 -l
  • exe 当前进程所对应的可执行程序,可执行程序.exe
  • cwd 当前进程所处的工作路径(current work dir)
  • 进程的PCB中会记录自己对应的可执行程序的路径

如果把可执行程序删除,为什么进程还可以运行❓

  • 因为删除是磁盘上的可执行程序,内存还有一份(如果可执行程序过大也会出问题)

怎样理解进程的当前工作路径(文件操作)❓>>>下面讲

ls /proc/1
ls /proc/

方法2:查看脚本

大多数进程信息同样可以使用top和ps这些用户级工具来获取。

  • ps查看当前系统的进程
  • axj显示进程的详细信息(可以打乱顺序写)
  • ps axj是用户级的工具,可以行输出消息
  • grep也是一个可执行程序
  • grep可以把数据按照 行过滤的方式输出

ps axj | grep myprocess :grep把得到的进程信息数据,按照行过滤的方式,凡是包含myprocess的关键字全部提取出来。


  • head n查看前n行的信息

ps axj | head -1 查看进程信息的属性信息


  • grep -v 关键字 就是除了显示除了关键字以外的其他行信息
  • -v反向过滤
  • 不需要查看grep的进程信息,举例很混乱。

ps axj | grep -v grep


  • && 一起查看,两个命令同时进程
  • | 管道
  • 查阅出来的信息也可能含有grep的进程信息,因为grep也是一个进程。grep这个进程本省也携带了过滤myprocess的这个关键字。

  • 周期性循环查看进程脚本
  • 清晰的看到进程的创建,执行,中止,状态这一整个连贯操作。

 while :; do ps axj | head -1 && ps axj | grep -v grep | grep process; sleep 1; done

ps axj | grep myprocess
ps axj | head -1
ps axj | head -1 && ps axj | grep 关键字
ps axj | head -1 && ps axj | grep -v 关键字 | grep 关键字
ps axj | head -1 && ps axj | grep -v grep | grep process
//循环查看脚本
while :; do ps axj | head -1 && ps axj | grep -v grep | grep process; sleep 1; done

方法3:系统调用获取进程标示符❗❗

  • 进程id(PID)>>>>>>获得子进程的id系统调用函数getpid()
  • 父进程id(PPID)>>>>>>获得子进程的id系统调用函数getppid()
  • 进程的pid:每一个进程都要有自己的唯一标识符,叫做进程pid
  • 进程的ppid(子进程的父进程的pid)
  • pid:process id 表示进程的唯一标识符
  • ppid:process parent id

  • pid和ppid同理
  • pid的类型是unsigned int,无符号整数被操作系统封装成pid_t的类型
  • pid是一个无符号整型的变量(unsigned int)
  • 每一个进程都有task_struct,每个进程间的task_struct怎么区分,用pid❗❗
  • Linux中PCB就是task_struct,进程 == 任务task
  • 用户想要知道一个进程的pid,是不能够直接到操作系统的内核数据结构中去查询的PCB的pid,需要使用操作系统提供的"系统调用接口"去查询

getpid() getppid()

  • 使用man指令去查询getpid 和getppid:man pid 
  • ❓若查询不到,请安装man命令的一个man-pages安装包
  • 安装查看:yum install man-pages(安装的时候只能用超级管理员root安装)
  • 系统调用函数getpid查看子进程的pid
  • 用getppid查看子进程的ppid == 父进程pid     
  • #include <sys/types.h>
  • #include <unistd.h>

理解:当代码预处理,编译,汇编,链接形成可执行程序的文件的时候是在磁盘中存放,这个时候还不是进程,也没有运行一行代码。只有可执行程序加载到内存,OS创建进程启动进程调度进程的时候。进程启动起来,系统调用函数才会获得PCB的pid。操作系统OS没有PCB,操作系统内核有定期的任务。比如刷新,内存管理之类这些有PCB,操作系统OS本身不需要PCB。

yum install man-pages //记得指令提权
man pid //ppid
man 2 pid //ppid

【用系统调用函数查看进程的pid】

  1 #include<stdio.h>
  2 #include <sys/types.h>
  3 #include <unistd.h>
  4 
  5 int main()
  6 {
  7     pid_t id=getpid();
  8     //让这个进程持续运行不停止
  9     while(1)
 10     {
 11         printf("I am a process!,pid=%d\n",id);     
 12         sleep(1);
 13     }
 14     return 0;
 15 }

终止进程 

  • ctrl+c就是在用户层面终止进程
  • kill -9 pid 可以用来直接杀掉进程 

创建进程(主fork)

进程创建的代码方式

(这里讲解重点在操作,轻微原理。后面会重谈进程的创建以及其他方法)

  • pid_t getpid(void);获得当前进程的pid
  • pid_t getppid(void);获得当前进程的父进程的pid
  • 父进程VS子进程
  • 运行 man fork 认识fork
  • fork有两个返回值
  • 父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)
  • 创建进程
  1. bash是父进程,进程创建进程
  2. 系统调用函数fork创建进程

🙂查看父子进程的pid 

  1 #include<stdio.h>
  2 #include <sys/types.h>
  3 #include <unistd.h>
  4 
  5 int main()
  6 {
  7     pid_t id=getpid();
  8     pid_t parentid=getppid();
  9     //让这个进程持续运行不停止
 10     while(1)
 11     {
 12         printf("I am a process!,pid=%d,ppid:%d\n",id,parentid);                      
 13         sleep(1);                                                     
 14     }                                               
 15     return 0;                                       
 16 } 

🙂进程创建/执行/终止

while :; do ps axj | head -1 && ps axj | grep -v grep | grep process; sleep 1; done

🙂多次重新启动进程查看pid和ppid

❓我们发现每次重新启动进程,进程的pid都要改变,但是进程的父进程pid都不改变。

❓查询父子进程属性信息:发现都有bash进程

  • 进程每次启动,对应的pid都不一样是正常的
  • 经过验证,我们创建的进程的父进程就是bash
  • bash是父进程,是实习生,是李婆,命令行解释器。
  • ❗所以进程可以创建进程。
ps axj | head -1 && ps axj | grep -v grep | grep 13185//查询父进程
ps axj | head -1 && ps axj | grep -v grep | grep 12345 //查询子进程随机一个

🙂fork创建子进程 

  • 创建一个进程,OS内多了进程,多了进程的PCB,内存多了进程的代码和数据。
  • 用户想要创建进程,就要创建内核数据结构,用户是没有权力对操作系统内部的内核数据结构等做增删查改的。
  • 所以,用户只能使用OS提供的系统调用函数来创建子进程。

fork:分支,叉子。创建子进程。

  • man fork查询
  • 头文件:  #include <unistd.h>
  • 返回值有两个类型都是pid_t
  • 可以接收返回值,也可以不接收。

fork创建子进程的脑中图,理解记忆❗

  • 在fork创建了子进程,是由父子进程分别都要执行后序代码,它们共享一份代码。
  • 创建一个进程,本质是系统中多一个进程,那么内存也会多一份代码和数据。
  • 父进程的代码和数据是从磁盘中加载过来的
  • 父进程的PBC也是OS动态创建的

子进程的代码和数据,PBC呢?

  • 子进程的代码和数据也可以从磁盘再加载一份到内存(下个博文进程控制会讲)
  • 在fork这里默认子进程的代码和数据是继承父进程的
  • 数据也是继承的吗❓
  • PCB是OS动态创建的

fork()之后,父子进程代码共享,但父子是并列关系

  • 理论上,子进程是在fork之后共享了父进程的代码和数据,OS才动态创建了子进程的PCB。
  • 父进程和子进程是并列的关系,它们是相互独立的,kill了父进程并不会影响子进程。(子进程会到后台)
  • 子进程有自己独立的PCB,在内核数据结构上和父进程的PCB是并列关系
  • 逻辑上是父子(仅仅停留在指针指向上面)

子进程的数据也是继承父进程的吗&父子写时拷贝

  • 理论上,子进程只能用父进程的数据
  • 但是,OS必须都做到保持进程的独立性,进程具有独立性!
  • 一个进程不能影响另外一个进程
  • 进程的数据:全局变量(静态区)
  • 进程的代码:只读(字符串常量)的部分
  • 所以子进程只会继承父进程的代码部分,而数据部分是拷贝。(因为数据需要独立分开,从而保证进程的独立性)
  1 #include<stdio.h>
  2 #include <sys/types.h>
  3 #include <unistd.h>
  4 
  5 int main()
  6 {
  7     printf("process is running,only me!\n");//只有父进程
  8     sleep(3);
  9     fork();//创建子进程
 10     printf("hello linux\n");
 11     sleep(5);                                                                                   
 20     return 0;
 21 }

  1 #include<stdio.h>  
  2 #include <sys/types.h>  
  3 #include <unistd.h>  
  4   
  5 int main()  
  6 {  
  7     pid_t id=getpid();  
  8     pid_t parentid=getppid();                                                                   
  9     printf("process is running,id=%d,parentid=%d\n",id,parentid);//只有父进程  
 10     sleep(3);                                                 
 11     fork();//创建子进程                                       
 12     printf("hello linux,id=%d,parentid=%d\n",id,parentid);    
 13     sleep(5);                                                                                                       
 22     return 0;                                                 
 23 }                                                             
~      

🙂fork的返回值

  • 我们为什么要创建子进程呢?

当然是为了两个进程执行不一样的代码, 想让子进程和父进程运行不一样的代码。

  • 怎样确保两个进程执行不一样的代码呢?

fork有两个返回值。可以提供给fork之后的两个不一样的逻辑。

  1. fork创建子进程成功,将子进程的pid返回给父进程,将0返回给子进程。
  2. fork创建子进程失败,则将-1返回给父进程,没有子进程。

❓C/C++语法层面上,if / else if /else 只能执行一个逻辑,而不可能同时执行两个逻辑。(可以改成全是if)

回答:因为历史学习,只有一个执行指令,是单线程。这里是存在多线程问题,后面谈谈。

  1 #include<stdio.h>
  2 #include <sys/types.h>
  3 #include <unistd.h>
  4 
  5 int main()
  6 {
  7     pid_t id=getpid();
  8     pid_t parentid=getppid();
  9     printf("process is running,id=%d,parentid=%d\n",id,parentid);//只有父进程
 10     sleep(3);
 11     pid_t ret= fork();//创建子进程
 12     if(ret == -1)
 13     {
 14         return 1;
 15     }
 16     else if(ret == 0)
 17     {
 18         printf("I am child process!,pid=%d,ppid:%d\n",id,parentid);
 19         sleep(2);
 20     }                                                                          
 21     else//ret>0
 22     {
 23         printf("I am parent process!,pid=%d,ppid:%d\n",id,parentid);
 24         sleep(2);
 25     }
        return 0;
   }

🙂why❓fork有两个返回值

在本篇我们先简述以下原因。这里重点掌握fork的应用即可。后序在学习了【地址空间】我们就可以深入的理解fork为什么有两个返回值了 。

❓ret的值可以是不同的值,同一个id,怎么可能既是 == 0又是 >0的呢

  • 虚拟地址空间,父子写时拷贝,TODP后面讲述。
  • id本身是一个变量,上面讲到父子进程的数据是独立的,所以父进程返回的数据是一个,子进程返回的数据是另外一个,所以id可以有两个值,只不过接收值的变量是同一个而已。

❓fork会有两个返回值,返回两次

  1. fork内部创建子进程的代码的时候,在return 返回pid之前就已经创建了子进程,且子进程已经被调度运行了。
  2. 所以准确来说,从fork内部从子进程创建开始fork内部到外的后面所有的代码均被子进程共享了(包括return)
  3. fork()是函数,是由OS提供的
  4. 当fork函数内部执行到return的时候,函数的核心工作已经完成了
  5. 所以,return 2次!

 

进程的当前工作路径

在我们以前C语言实现文件操作的时候,我们用写的方式打开一个文件,OS默认就是在当前路径下创建了这个文件。

  • 在这个场景当中新建文件在当前路径/当前目录下 == 这个进程的当前工作目录
  • 创建写入数据到log.txt,OS执行这个代码就会创建进程,创建到当前路径其实就是进程的当前工作路径。
  • 更改进程的当前工作目录:chdir
  • 当前工作路径不是一个C语言概念的问题,而是一个操作系统的问题OS的问题。
fopen("log.txt","w");
#include<stdio.h>
int main()
{
  FILE *fp = fopen("log.txt","w");
  (void)fp;//ingor warning
  return 0;
}

❓能否改变进程的当前工作目录

  • chdir 更改进程工作的目录
  • man chdir
  • 开启文件>>>>记录进程的启动路径>>默认创建文件会拼接路径>>拼接进程的当前工作目录>>>创建文件

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>

int main()
{
   chdir("/home/tangsiqi/usr"); //更改路径
   FILE *fp = fopen("log.txt","w");
   (void)fp;//ingor warning
   fclose(fp);
   return 0;
}

🙂感谢大家的阅读,若有错误和不足,欢迎指正。 

  • 18
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

唐唐思

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值