本节我们来初步认识一下进程,有些要深入研究的知识,等到后面进程部分讲解
初识进程
1、什么是进程
一个运行起来(加载到内存)的程序叫做进程
在内存中的程序叫做进程(这样说也是正确的)
这也就是我们前面为什么说要把一个程序加载到内存里面
当然,如果是没接触过进程的人可能只是知道上面语句的意思,但是不理解。不着急,下面会一步步解释的
首先我们知道,程序的本质:就是文件,而文件存放在磁盘中
我们之前都是一次执行一个程序,也就是把一个程序加载到内存中。但是,如果有多个程序共同加载到内存中呢?
每个程序在内存中对应的存放位置,什么时候被执行,什么时候执行结束销毁?等等都要被操作系统所管理,既然是管理,那么就要遵循管理的方法先描述,后组织
所以,要进行管理进程,那么就要遵循先描述,后组织的管理方法
因为要先描述,所以我们可以像上一节讲计算机内部结构一样,把所有文件的相同数据/属性都整理到一起,统一放到struct或者class(结构体或者类)当中。而内存中的这个结构体就叫做PCB
,也称之为进程控制块(Linux操作系统下的PCB是:task struct)
也就是说:操作系统不是对可执行程序直接操作的,而是对进程的PCB(进程控制块)进行操作。在PCB内部,通过优先级的方式,找到操作系统需要的属性/数据之后,PCB将对应的可执行程序的代码交给操作系统执行。当PCB的对象内部有属性死亡之后,先释放可执行程序中的代码,然后再释放掉PCB的对象(也就是释放链表中的节点),此时,进程就被释放掉了
小结:
先描述:构建PCB(进程控制块)
在组织:对进程进行管理,变成了对进程对应的PCB进行相关的管理(对进程的管理 -> 对链表的增删查改)
2、见见进程
windows下的进程:
打开任务管理器(ctrl+alt+del)
这些都是电脑中正在运行的进程
linux下的进程:
makefile:
myproc:myproc.c
gcc -o myproc myproc.c
.PHONY:clean
clean:
rm -f myproc
ps ajx
ps ajx :显示所有进程
ps sjx | head -1 && ps ajx | grep "myproc"
kill -9 +进程id
kill -9 +进程id :终止对应的进程(有的进程ctrl +c/+d不能停止)
小结:
进程在调度运行的时候,进程就具有动态属性
kill -l
kill -l : 查看对应的信号编号
见见与进程有关的系统调用
getpid
myporc.c
while(1)
{
printf("我是一个进程!, 我的ID是: %d", getpid());
sleep(1);
}
另一种查看进程的方式:
/proc——==进程的信息可以通过 /proc 系统文件夹查看==
这些数字开头的就是对应的进程的PID
在进程执行的期间,即便是可执行程序被删除了,进程也不会停止,会一直执行:
getppid
while(1)
{
printf("我是一个进程!, 我的ID是: %d, 父进程pid: %d\n", getpid(), getppid());
sleep(1);
}
小结:
命令行上启动的进程,一般它的父进程没有特殊情况的话,就是bash!
我们启动linux就会自动产生一个bash,以及对应的pid。我们再linux下面执行的ls等命令或者./a.out等操作都是父进程生成的子进程来进行操作的
这里就引出两个问题来了:
1、父进程怎么创建子进程的?
2、子进程是怎么运行起来的?
这两个问题现在解释清楚很难,我们后面会解释
现在就先来看看父进程如何创建子进程的
创建子进程
这里要用到fork()函数,我们可以通过man fork来查看该函数怎么使用
在底行模式输入:/return val查看fork返回值
翻译过来就是:
如果fork调用成功,那么把子进程的pid返回给父进程,把0返回给子进程;
如果调用失败,-1返回父进程,没有子进程生成
注意:也就是说,fork函数有两个返回值!!!
pid_t id = fork();
while(1)
{
printf("子进程, pid: %d, ppid: %d, id: %d\n", getpid(), getppid(), id);
sleep(1);
}
看起来好像是这样,与文案的描述一样,但是:
同一个变量id, 在后续不会被修改的情况下,竟然有不同的内容!,并且下面还要更奇怪的现象
myproc.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
// 创建子进程 -- fork是一个函数 -- 函数执行前: 只有一个父进程 -- 函数执行后: 父进程+子进程
pid_t id = fork();
if(id == 0)
{
//子进程
while(1)
{
printf("子进程, pid: %d, ppid: %d, id: %d\n", getpid(), getppid(), id);
sleep(1);
}
}
else if(id > 0)
{
// parent
while(1)
{
printf("父进程, pid: %d, ppid: %d, id: %d\n", getpid(), getppid(), id);
sleep(2);
}
}
else
{
}
// 同一个变量id, 在后续不会被修改的情况下,竟然有不同的内容!
//printf("我是一个进程, pid: %d, ppid: %d, id: %d\n", getpid(), getppid(), id);
sleep(2);
// while(1)
// {
// printf("我是一个进程!, 我的ID是: %d, 父进程pid: %d\n", getpid(), getppid());
// sleep(1);
// //int a = 1/0;
// }
// return 0;
}
这里,if和else if语句竟然同时执行了!并且,两个while死循环也同时执行了!!!(这里也解释不清楚,等后面学了进程地址空间再来解释)
这在C/C++语言根本不敢想象
原因:
1、fork()之后,会有父进程+子进程两个程序在执行后续代码
2、fork()后续的代码,会被父子进程共享。通过返回值不同,父子进程执行后续共享代码的一部分。数据各自开辟空间,私有一份(采用写时拷贝)。
总结
可以看到,系统与我们前面学习的语言区别还是很大的,光一个fork函数就不能通过语言来解释清楚,所以不能局限于语言,其他各个方面也要学好!