1.进程介绍与概念
进程的本质是在计算机内存中运⾏的程序,但是这⼀个概念太过于⼴泛
每个应用程序运行于现代操作系统之上时,操作系统会提供一种抽象,好像系统上只有这个程序在运行,所有的硬件资源都被这个程序在使用。这种假象是通过抽象了一个进程的概念来完成的,进程可以说是计算机科学中最重要和最成功的概念之一。
进程是操作系统对一个正在运行的程序的一种抽象,换言之,可以把进程看做程序的一次运行过程;同时,在操作系统内部,进程又是操作系统进行资源分配的基本单位。
要了解进程,⾸先采⽤前⾯在操作系统基础中介绍的思想「先描述,再组织」,每⼀ 个进程本质也是⼀个结构(⼀般称为PCB(Process Control Block,进程控制 块),在Linux下的PCB称为 task_struct ),计算机将程序加载到内存,就会在 内存中添加⼀个进程,那么此时就会形成⼀个结构体,该结构体中存在进程的⼀些描 述,例如进程的在内存中的位置、进程的编号、对应数据和代码的地址等,在程序执 ⾏前,进程会被排列到⼀个数据结构,通过操作系统调度器从数据结构中取出进程就 代表对应的进程获取到CPU的使⽤权,此时就会通过对应进程的结构体中的相关数据 加载代码和数据进⾏运算、逻辑处理,从⽽到达执⾏程序的效果
这⼀过程可以看出,进程=程序代码和数据+进程结构体 进程可以粗略得分为两种:
1. 执⾏完对应代码就退出
2. ⼀直运⾏在后台,也被称为「常驻进程」
2.Linux下查看进程
⾸先,通过下⾯的C语⾔代码创建⼀个进程:
#include <stdio.h>
#include <unistd.h>
int main()
{
while(1) {
printf("hello world\n");
sleep(1);
}
return 0;
}
对应的Makefile
代码如下:
TARGET=process
SRC=process.c
$(TARGET):$(SRC)
gcc -o $@ $^
.PHONY:clean
clean:
rm -rf $(TARGET)
编译运⾏前⾯的C语⾔代码,并在Linux下查看对应进程
可以使⽤下⾯的⽅式:
ps ajx | head -1 && ps ajx | grep process
# 如果存在部分干扰信息,可以使用结合管道和grep的-v指令对部分干扰信息的关键字进行过滤
上⾯的指令中的 ps ajx代表查看当前系统运⾏时所有的进程以及部分信息,直接执 ⾏就可以看到进程信息
可以看到对应的进程为:
在Linux下实际上进程都被保存在⼀个名为 proc 的⽬录下,这个⽬录不是存在于磁 盘中的⽬录,⽽是操作系统运⾏时进程出现创建的,使⽤下⾯的命令查看该⽬录:
ls /proc
proc ⽬录在根⽬录下,与 home ⽬录同级
再次运⾏前⾯的C语⾔代码,使⽤下⾯的代码查看程序进程信息以及在 proc下的位置
3.结束进程的方式
在Linux下,可以使⽤两种⽅式结束进程:
1. 快捷键 ctrl+c
2. 使⽤ kill 指令: Kill -9 进程PID
4.进程对应程序文件位置
在前⾯C语⾔程序对应的进程⽬录中除了可以看到 cwd 以外,还可以看到⼀个名为 exe 的软链接,该链接指向着⼀个路径,如下:
该路径对应的即为加载到内存中的代码和数据(进程组成的⼀部分),如果在运⾏时 将该路径下的 process ⽂件删除,此时已经运⾏的进程不会受到影响,因为对应的 代码和数据已经加载进内存,受影响的只是下⼀次⽆法直接使⽤运⾏程序的⽅式创建 进程
删除后再次查看结果如下:
5.进程PID与 PPID
PID 是Linux系统下每⼀个进程对应的⼀个编号,该编号会因为进程产⽣的时间不同 ⽽不同,例如每⼀次运⾏前⾯的C语⾔代码都会得到⼀个不同的PID
如果想获取到当前进程的 PID 可以使⽤getpid()函数 该函数原型如下:
pid_t getpid(void);
其中p id_t 类似于 long 类型,是⼀个⻓整型,该函数不需要传递形参
在前⾯的C语⾔代码中添加该函数并打印该进程的 PID 对⽐使⽤指令查看进程信息下的PID
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t id = getpid();
while(1) {
printf("hello world, pid = %d\n", id);
sleep(1);
}
return 0;
}
可以看到PID⼀致
⼀般连续创建的进程,PID 是连续的 PID 是进程的编号 PPID 是指定进程的⽗进程的编号,当前进程也被称为对应⽗进程的⼦进程
同样,在代码中想获取到当前进程的⽗进程对应的 PPID ,可以使⽤ getppid() 函 数,原型如下:
pid_t getppid(void);
对前⾯C语⾔的代码进⾏修改,如下:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t id = getpid();
pid_t pid = getppid();
while(1) {
printf("hello world, pid = %d ppid = %d\n", id, pid);
sleep(1);
}
return 0;
}
多次运⾏代码后结果如下:
可以发现,每⼀次运⾏代码, PID 都会改变,但是 PPID 始终不变,因为其⽗进程始 终是同⼀个进程,通过查看进程的指令可以找到 PID 为14901对应的⽗进程:
bash 是Linux下的指令解释器,每⼀次执⾏指令时,实际上就是将对应的指令交给 了bash,由 bash 再交给操作系统进⾏执⾏,当执⾏上⾯程序的进程时,实际上是 创建了⼀个 bash 的⼦进程,再由⼦进程执⾏对应的程序代码和数据
6.创建子进程 fork
6.1简单了解 fork函数
fork 函数原型如下:
pid_t fork(void);
在Linux编程⼿册中对 fork 的解释如下:
对应的函数返回值的解释如下:
⼤意为:成功的情况下,该函数返回⼦进程的 PID 给⽗进程,返回0给⼦进程。失败 的情况下,该函数返回-1给⽗进程,不创建任何⼦进程,并恰当设置错误代码
6.2简单使用 fork函数
根据前⾯对 fork 函数的介绍,修改前⾯的C语⾔代码如下:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t id = fork();
if(id > 0) {
printf("I am prarent process, my pid = %d my ppid = %d\n", getpid(), getppid());
} else if(id == 0) {
printf("I am child process, my pid = %d my ppid = %d\n", getpid(), getppid());
}
return 0;
}
运行结果如下:
可以看到,⼦进程的 PPID 即为⽗进程 PID ,与前⾯的思想对应
7.简单了解 fork细节
上⾯的代码中⼀共存在着三个待解决的问题:
1. 使⽤ fork 创建⼦进程,为什么可以通过if 和 else if区分⼦进程和⽗进程
对于第⼀个问题来说,根据 fork 函数的返回值描述,当函数运⾏成功的情况下,会 返回⼦进程的 PID 给⽗进程, PID ⼀般都是⼤于0的,⽽⼦进程会收到 返回值为0,所以当上⾯代码中的id变量⼤于0时,就会⾛ fork 函数的 if 语句,⽽唯⼀⼀个接 收到的id⼤于0的就是⽗进程;同理,⼦进程的id变量等于0,就会⾛ else if 语句。
2. 为什么 fork 函数出现了两个返回值
对于第⼆个问题来说,实际上执⾏ fork 函数时,在返回 pid 之前就已经执⾏完了创 建⼦进程逻辑,此时⽗进程和⼦进程共享代码,但是各⾃独⽴数据,⽗进程拥有⾃⼰ 的返回值,⼦进程也拥有⾃⼰的返回值,所以此时 return 的时候是每个进程返回各 ⾃的返回值
3. 为什么两个分⽀语句if和 else if都执行了
对于第三个问题来说,当代码执⾏到 fork 函数时,会执⾏ fork 函数的内部逻辑, 再执⾏完其主逻辑代码后,就已经创建出了⼦进程(对应存在⼀个⼦进程的 task_struct ),此时⼦进程会与⽗进程共享同⼀块代码,但是⼦进程拥有⾃⼰的 数据,并使⽤⾃⼰的数据执⾏代码。⽽之所以需要共享同⼀块代码,是因为⽗进程的 代码是从硬盘中加载的,但是⼦进程的代码并不存在与硬盘,从⽽也就⽆法加载,⽽ 之所以不同的进程有着不同的数据,这是进程独⽴性的主要体现,可以⽤下图先简单 理解这个过程,具体过程会在进程地址空间讲解:
此处,⼦进程的 task_struct 实际上先是对⽗进程的task_struct 的拷⻉,再对其中的内容进⾏⼀定的修改;