✨个人主页: 熬夜学编程的小林
💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】【Linux系统编程】
目录
1、进程的基本概念
2、描述进程-PCB
2.1、什么是PCB
2.2、为什么要有PCB
3、task_ struct
3.1、启动进程
3.2、创建进程
3.3、一次创建多个进程
3.4、task_ struct内容分类
3.5、查看进程内容
总结
1、进程的基本概念
课本概念:程序的一个执行实例,正在执行的程序等。
内核观点:担当分配系统资源(CPU时间,内存)的实体。
1. 如何用通俗易懂的话来理解进程呢???
进程 == PCB(进程控制块) + 进程对应的的代码和数据。一个进程对应一个PCB 。
注意:可执行程序加载到内存不是进程,只是进程对应的代码和数据。
2、描述进程-PCB
2.1、什么是PCB
进程信息被放在一个叫做PCB(进程控制块)的数据结构中,可以理解为进程属性的集合。
课本上称之为PCB(process control block), Linux操作系统下的PCB是: task_struct。
task_struct 是 PCB的一种,在Linux中描述进程的结构体叫做task_struct。
task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。
struct PCB
{
//所有属性
struct PCB* next;
//内存指针
}
因此对进程的管理变成了对链表的增删查改。
2.2、为什么要有PCB
因为操作系统要对进程进行管理,管理需要先描述再组织,而PCB是对进程的描述。
3、task_ struct
3.1、启动进程
1、./可执行程序,本质就是让系统创建进程并运行(包括:自己写代码形成的可执行程序,系统命令,可执行文件)。在Linux中运行的大部分指令操作,本质就是运行进程!!!
2、每一个进程都要有自己唯一的标识符,叫做进程的pid(进程id)。
3、一个进程,想知道自己的pid???
方式一:通过指令查看pid
ps指令:
语法:
ps [选项]
功能:
显示当前终端会话中属于当前用户的进程列表。
常见选项:
-a : 显示跟当前终端关联的所有进程
-j : 工作格式
-x : 显示所有进程,不以终端机来区分
此处使用固定格式查看:
ps -ajx | head -1 && ps -ajx | grep 可执行文件
# &&表示前后命令都要按照顺序执行,此处表示先查看该命令的第一行再查看有关可执行程序的信息
方式二:通过调用系统函数
操作系统对进程进行管理,但是用户不能直接访问操作系统,因此需要通过系统提供的系统调用函数来管理进程。
查看pid的函数为getpid();
可以通过man手册进行查询,输入命令: man getpid
通过创建一个C语言代码来查看pid:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
while(1)
{
printf("I am a process,pid: %d\n",getpid());//查看pid
sleep(1);//打印一次之后休眠一秒
}
return 0;
}
4、ctrl + c 在用户层面终止进程 kill -9 pid 杀掉进程
注意:kill掉任意一个进程不会影响另一个进程!!! (进程保证独立性)
5. ppid (父进程id)
前面获取pid的函数后面还有ppid函数,此处通过代码查看ppid。
为什么进程每次启动pid会变但是ppid不会变呢???
我们先查看一下父进程是什么,输入该命令:
ps -axj | head -1 && ps -axj | grep ppid
通过查看可以看到该进程是bash进程(命令行解释器),因此就很好理解了。
- 当我们运行一个进程时,命令行解释器会把这个指令解释成bash的子进程。
- 接着再由这个bash的子进程执行对应的命令。
- 即:每一条命令被执行,都属于bash的子进程,只是子进程不一样。
如何理解子进程不一样呢?
就像我们高考考上北京大学,当年会给我分配一个学号,但是有一天我不想读了,我又重新高考进入北京大学,此时的学校还是北京大学,但是学号就不一样了。
补充:
为了更好看到执行程序与进程信息,可以使用shell脚本,隔一秒查一次进程
while : ; do ps ajx | head -1 && ps ajx | grep myprocess; sleep 1; done
3.2、创建进程
创建跟上面查看进程一样,需要调用系统提供的函数。创建进程的函数为fork();fork之后,父子代码共享。
使用man手册查看fork()函数,输入命令:man fork
创建一个进程,本质是系统中多了一个进程,多一个进程就多一个内核数据结构+自己的代码和数据。父进程的代码和数据从硬盘上加载来的。子进程的代码和数据从哪来呢?? 默认情况继承父进程的代码和数据。
怎么证明是子进程呢???
使用C语言代码创建进程来证明:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
fork();//创建进程
printf("hello world,pid: %d,ppid: %d\n",getpid(),getppid());//查看进程对应信息
return 0;
}
为什么要创建子进程?
我们想让子进程执行和父进程不一样的代码。提高运行效率。
接下来我们就对父子进程写入不一样的代码,此处需要用到fork函数的返回值。
继续使用C语言代码来实现:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
printf("process is running,only me!,pid: %d\n", getpid());
sleep(3);
pid_t id = fork();
if (id == -1) return -1; //进程创建错误直接退出
else if (id == 0)
{
//child 子进程代码
while (1)
{
printf("id: %d,I am child process,pid: %d,ppid: %d\n", id, getpid(), getppid());
sleep(1);
}
}
else
{
//parent 父进程代码
while (1)
{
printf("id: %d,I am parent,pid: %d,ppid: %d\n", id, getpid(), getppid());
sleep(2);
}
}
return 0;
}
1.不同进程执行不同的代码是实现出现了,但是同一个id怎么可能既是等于0,又是大于0?
此处与虚拟地址空间,父子进程写时拷贝有关,暂时不做讲解。
2. fork有两个返回值怎么解释呢???
fork是一个函数,只不过是OS(操作系统)提供的。
函数内部父子进程已经存在。可以被调度了。
由于在复制时复制了父进程的堆栈段,所以两个进程都停留在fork函数中,等待返回。因此fork函数会返回两次,一次是在父进程中返回,另一次是在子进程中返回,这两次的返回值是不一样的。
3.3、一次创建多个进程
创建多个进程实际就是多次使用fork函数即可,此处继续用C语言代码进行实现。
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
void RunChild()
{
while (1)
{
printf("I am a child process,pid: %d,ppid: %d\n", getpid(), getppid());
sleep(1);
}
}
int main()
{
const int num = 5;
for (int i = 0; i < num; i++)
{
pid_t id = fork();
if (id == 0)
{
RunChild();//运行子进程代码
}
sleep(1);
}
while (1)
{
sleep(1);
printf("I am parent,pid: %d,ppid: %d\n", getpid(), getppid());
}
return 0;
}
补充:
为了更好看到执行程序与进程信息,可以使用shell脚本,隔一秒查一次进程,且不查看grep进程信息。
while : ; do ps ajx | head -1 && ps ajx | grep myprocess | grep -v grep; sleep 1; done
3.4、task_ struct内容分类
★ 标示符: 描述本进程的唯一标示符,用来区别其他进程。
★ 状态: 任务状态,退出代码,退出信号等。
★ 优先级: 相对于其他进程的优先级。
★ 程序计数器: 程序中即将被执行的下一条指令的地址。
★ 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
★ 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
★ I/ O状态信息: 包括显示的I/O请求,分配给进程的I/ O设备和被进程使用的文件列表。
★ 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
★ 其他信息
3.5、查看进程内容
ls /proc/pid -d # 按照目录查看
ls /proc/pid -l # 查看进程内容
使用一个简单的C语言代码测试:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
while (1)
{
printf("I am process,pid: %d,ppid: %d\n", getpid(), getppid());
sleep(1);
}
return 0;
}
注意:只有运行可执行程序才能查到该进程目录。
该进程目录有很多内容,此处只截取了一部分内容,暂时我们需要知道的是两个链接文件,cwd(表示进程当前工作路径)---current work dir,exe(可执行程序路径)。
1. 如果我们在此处把可执行程序给删除,进程还会不会运行呢???
根据我们的现象是,进程还会运行,而且可执行程序也确实被删除了。进程还能继续运行的原因是,删除的是硬盘上的可执行程序,而原则上内存中还有该可执行程序(该可执行程序大小小于内存大小),但是进程超过内存的大小,运行则就可能出问题。
2. 当前工作路径有什么用呢?
我们在C语言中学习的文件操作,fopen("log.txt","w");默认是在当前目录创建文件,但是我们不一定每次都在当前目录创建文件,那怎么才能在其他目录下创建文件呢?这就是当前工作目录的意义。
修改当前工作路径需要用到一个函数,输入命令:man chdir
测试代码:
#include<stdio.h>
#include<unistd.h>
int main()
{
chdir("/home/jkl");//更改工作目录为/home/jkl
FILE* pf = fopen("log.txt", "w");//创建文件
(void)pf;//忽略警告
fclose(pf);
while (1)
{
printf("I am a process,pid: %d\n", getpid());
sleep(1);
}
return 0;
}
每个进程在启动的时候,会记录自己在哪个路径下启动,进程的当前路径。
fopen("log.txt","w"); 即会在进程的当前路径下新建文件。
总结
本篇博客就结束啦,谢谢大家的观看,如果公主少年们有好的建议可以留言喔,谢谢大家啦!