一.进程和程序的理解
首先抛出结论:进程是动态的,暂时存在于内存中,进程是程序的一次执行,而进程总是对应至少一个特定的程序。
程序是静态的,永久的存在于磁盘中。
程序是什么呢?程序其实就是存放在我们磁盘上的可执行程序,它静态的,而且是永久存在的。
进程就是磁盘上的程序,加载拷贝到内存上运行起来的,这就是进程。
在windows上打开任务管理器,我们就可以看到操作系统中运行的所有的进程。
这里存在着很多进程,这么多进程,操作系统要不要将这些进程给管理起来呢?答案是肯定的。那又要如何管理呢?这就引出了之前说的冯诺依曼体系结构中的,先描述,再组织。
那要怎么将这些进程描述好,组织好呢?首先操作系统是C语言写的,C语言中描述一个物体的很多属性,都是通过结构体struct概括起来的。而描述进程的这个结构体的全部叫做:PCB。
二.进程的PCB
进程的PCB全称叫做进程控制块(process control block),PCB中部分内容如下:
struct xxxx
{
//id(也就是pid)
//代码数据地址
//优先级
//程序计数器(pc)
//struct PCB*next
}
标示符:描述本进程的唯一标示符,用来区别其他进程。(pid)
状态:任务状态,退出代码,退出信号等。
优先级:相对于其他进程的优先级。
程序计数器:程序中即将被执行的下一条指令的地址。(pc)
内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
上下文数据:进程执行时处理器的寄存器中的数据。
I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和正在被进程使用的文件列表。
记账信息:可能包括处理器时间总和,使用的时钟总数,时间限制,记账号等。
其他信息等等……
进程的运作:
所以说:进程=可执行程序+内核数据结构(PCB)。
在Linux中,进程的PCB具体是struct task_struct{}
其中可执行程序已经被描述成了PCB,然后操作系统需要将PCB给组织起来,使用链表进行链接,于是对于进程的管理就成了对链表的增删改查。
这里的链表又和之前学习的双向头尾链表有点不同。
struct task_struct//PCB
{
//各种属性
struct dlist list;
}
struct dlist//链表
{
struct dlist*prev;
struct dlist*next;
}
PCB中有一个结构体对象list,然后该结构体中有prev和next指针将操作系统中PCB给组织起来。
这里不要认为进程的PCB只能存在链表中,操作系统中存在许多组织进程PCB的列表,像运行队列,阻塞队列等等。
后续细说。
三.task_struct中的pid和ppid
1.pid
task_struct中有很多属性,也就是很多字段,那它的核心字段有哪些呢?-贯彻整个的学习的过程。
这里我要说到一个核心字段pid(process id),也就是标识该进程的一个数字,表明它是独一无二的一个进程。
这里我们写一个小小的代码来看看进程的pid。
①test.c文件中,写一个死循环的程序。
#include<stdio.h> #include<unistd.h>
int main()
{
while(1)
{
printf("这是一个进程\n");
sleep(1);
}
return 0;
}
②makefile自动化构建代码。
mycode:test.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f mycode
然后我们使用make创建出可执行程序,然后执行该可执行程序,于是该可执行程序就变成了进程。
这里鼠标右键,点击复制SSH渠道,为了方便我们观察进程的运行和进程的pid的变化。
使用脚本查看该进程的pid:ps ajx | head -1 && ps ajx | grep mycode
该进程我们写的死循环,它就在一直运行起来的,每次我们使用这个这个脚本查看进程,可以看出mycode进程的pid一直没变过,因为该进程一直在运行。而每次使用该脚本,我们都会使用grep这个程序,一瞬间,grep进程就运行起来并马上死亡,于是看到每次grep,它的pid都会发生变化。
在左边进程一直在运行,可以使用ctrl+c,终止该进程,再使用该脚本查看,就发现该进程pid就看不到了。
还可以使用这个命令来杀死进程:kill -9 进程的pid(无脑杀死进程,后续说的僵尸进程没有办法杀死)
2.ppid
这里可以看到除了进程的pid之外,进程还有ppid,而且ppid的值一直没有变过。
解释:进程中除了自己的pid之外,还有父进程的pid,父进程的pid,在子进程来看就是自己的ppid。
那这个父进程到底是谁呢?父进程其实是bash,也就是我们的命令行解释器。
bash是一直不变的,我们在bash下创建的进程,都是bash的子进程。
马上我们使用函数getpid()和getpppid()就验证了。
四.系统调用接口getpid()和getppid()
上述中,我们使用脚本来查看一个进程的pid,每次都这样子来查看进程的pid就很麻烦,于是有两个系统调用接口就可以直接查看进程的pid和ppid,现在我们直接演示。
注意系统调用接口函数在man 2号手册里面查找,而各种库函数都是在man 3号手册中查找的。
修改源代码:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
while(1)
{
printf("这是一个进程,pid:%d,ppid:%d\n",getpid(),getppid());
sleep(1);
} return 0;
}
这里即验证成功,该进程的ppid和bash进程的pid是对应上的,故而说明,在bash下创建出来的进程都是bash的子进程。
五./proc/pid目录和PCB的关系
linux中有一个目录proc,它存放着所有的进程。
其中proc/pid目录存放着具体某个进程的一些信息。目录的名称就是以该进程的pid命名的。
当我们将进程启动起来,使用ls /proc/pid命令就可以查看该进程的一些信息。
其中cwd就是该进程的当前工作目录,而exe就是可执行程序,还有很多信息。
当ctrl+c终止掉进程之后,再使用查看同样的目录,该目录就不存在了,由此可以看出,/proc目录结构是动态的。
当进程运行起来的时候,我们使用命令make clean将可执行程序删除了(也就是在磁盘上删除了该可执行文件),但是进程此时并不受影响,因为进程已经在内存中运行起的。
然后使用命令查看该进程的信息,可以看到该进程是知道自己的可执行程序是被删除了的。
于是得出一个结论:进程是可以找到自己的可执行程序的,靠的就是cwd这个链接文件。
在/proc/pid下我们可以看到具体某个进程的信息,那么它和进程的PCB的关系是什么呢?
①/proc/pid
目录下的内容是操作系统向用户展示的关于进程的一部分信息,它与进程控制块(PCB)有一定的关联。
②/proc/pid
中的信息可以被看作是从 PCB 中提取出来的、以可读形式呈现给用户的部分关键数据。这些数据反映了进程的某些状态和特征,是 PCB 中部分重要信息的外在表现。
③例如,/proc/pid/status
文件中的一些字段可能与 PCB 中记录的进程状态信息相对应;/proc/pid/maps
可能与 PCB 中关于进程内存布局的信息相关。
④然而,/proc/pid
并不能涵盖 PCB 中的所有详细和底层的控制信息,只是提供了一个便于用户观察和了解进程基本情况的接口。
六.Linux中创建进程的两种方式
1.手动的创建进程
上述的所有过程我们都是手动的创建出进程。
除了手动的创建出进程,我们还可以使用代码来创建出进程。
2.使用代码进行创建
使用函数fork()来创建出子进程。(后续细说)