在我们有了进程的初步基本概念之后,我们就要来学习一下如何创建进程,进程的几种状态,进程的优先级的问题,搬好小板凳要开讲了……
本文实验系统:CentOS 7.6
~
父进程:指已创建一个或多个子进程的进程~
我们通过getpid
函数来获取当前进程的ID,也可以通过getppid
来获取父进程的ID:
#include
#include
#include
int main()
{
while(1)
{
printf("I am a process! pid: %d ppid: %d\n",getpid(), getppid());
sleep(1);
}
return 0;
}
我们多次执行上述代码观察当前进程和其父进程的ID:
之前的只是我们知道了,当每次执行一个可执行程序之后,进程的ID都会改变,上图也验证了这一点,但是我们惊奇的发现,为啥父进程的ID始终都是一个值,一直都是不变的呢??
几乎我们在命令行上所执行的所有的指令(你的cmd),都是bash进程的子进程!
衍生问题:
我们后面再说…
fork函数是用来创建子进程的,它有两个返回值。
这就有点违背我们之前学习的认知了,因为我们之前接触的函数有返回值的也就只有一个返回值,从来没听过有两个返回值的说法。
代码演示:
#include
#include
#include
int main()
{
pid_t id = fork();
printf("Hello World! id = %d\n", id);
return 0;
}
运行的结果非常的诡异:一条打印语句竟然有两个打印结果,并且,我们并没有对id
值做任何修改,但是结果却是有两个不一样的id
值。
这在我们之前的编程语言学习中是不可能的,我们是不接受的。
我们再来看一段代码:
#include
#include
#include
int main()
{
pid_t id = fork();
//id == 0 : 子进程 , id > 0 : 父进程
if(id == 0)
{
while(1)
{
printf("我是子进程,我的pid:%d,我的父进程是:%d\n", getpid(), getppid());
sleep(1);
}
}
else
{
while(1)
{
printf("我是父进程,我的pid:%d,我的父进程是:%d\n", getpid(), getppid());
sleep(1);
}
}
return 0;
}
if
和else if
可以同时执行吗?不能!!但是上述代码执行结果却是和我们C语言中相违背了~
如何解释呢?fork如何做到会有不同的返回值?
既然是有两个返回值,那么一定是曾经被返回了两次,要不然怎么可能有两个返回值呢?
要想弄清楚这个问题,我们就要知道一个C语言阶段的知识:
调用一个函数,当这个函数准备return的时候,这个函数的核心功能完成了吗??
答案是肯定的!!
这也就实现了,通过不同的返回值,让不同的进程执行不同的代码。
父进程return一次,子进程return一次,不就是两次返回吗,不就有两个返回值吗?
fork()为什么给父进程返回子进程的pid,给子进程返回0?
在之前学习过的二叉树中,我们知道一个父结点可以有多个孩子结点,但是一个孩子结点只能有唯一的父亲节点。
补充:
ls
为例:ls
变成进程之后,该进程的代码就是从磁盘/usr/bin/ls
路径下读取数据代码。操作系统就像是计算机里的哲学一样,因为操作系统这门学科讲的范围很宽泛,它的理论内容适用于各个操作系统,而我们要具体的学习某一款操作系统那就是
Linux
。
首先我们凡是说进程,就必须先想到进程的task_ struct
。
我们来看一下进程的几种状态:
我们先来看一下进程状态之间的关系:
上面一大堆乱七八糟的,看上去是真的乱,真让人头大,我们慢慢来讲~……
概念:
一个进程被运行,操作系统当中每个
CPU
,系统都会个CPU创建一个runqueue
,所以一个进程想被调动,说白了就是将自己的进程放到运行队列当中就可以。
如何理解进程被运行:
进程只要在运行队列中叫做运行态,不代表正在运行,代表我已经准备好了,随时可以调度!!
是该进程还在,只不过永远不运行了,随时等待被释放!!
进程终止状态:进程已完成执行。
终止就是最终资源释放,但是PCB控制块至少还在,要让操作系统来释放。
重点:
当进程访问某些资源(磁盘网卡),该资源如果暂时没有准备好,或者正在给其他进程提供服务,此时:
runqueue
中移除。网卡,声卡,显卡每一个操作系统都要先管理起来,所以操作系统要先描述再组织~
当设备在硬件层面准备好了,一定会通过某种方式让操作系统知道,再将该进程的PCB放在CPU的运行队列当中。
补充:
我们来看一下S状态:
#include
#include
#include
int main()
{
while(1)
{
printf("I am a process: %d\n", getpid());
}
return 0;
}
总结:
虽然一直在刷屏,但是还是S状态,原因也是在printf上,不断地往显示器上打印,但是显示器是个外设速度非常慢,即便是闲着准备好被刷新也需要花费时间。这个进程90%的情况都是在等,在等显示器就绪。
只有光是一个死循环,不调用外设的时候才是一直处于R状态。
概念:
一个进程对应的代码和数据,被操作系统因为内存资源不足而导致操作系统将该进程的代码和是数据临时的置换到磁盘当中,此时叫做进程挂起。
往往内存不足的时候,伴随着磁盘被高频率访问!
首先D状态也是一种阻塞状态,而我们上述的S状态也是阻塞状态,只是D状态是一种深度睡眠~
deep - 深 / disk - 磁盘
的意思假设场景:
当一个进程向磁盘写文件的时候,由于要写的文件很大,所以进程要在那里等,如果等的时间太长了的话,操作系统见到一个进程在那里很悠闲直接把它干掉了,那等磁盘将文件写完之后,回头一看,傻眼了,进程不见了,那写入的文件怎么处理呢??如果该文件写入失败了,结果返回的时候发现进程不见了,那么数据就丢了,后果很严重。
尽然进程要等,就是要等一个返回值,就是为了判断文件写成功了没!!
所以这个进程不能随便杀掉,所以操作系统就将该进程设置成了D状态!!
不过这种情况不多见,很难能见到~
补充:
僵尸进程:Z - zombie
当一个Linux中的进程退出的时候,一般不会直接进入X状态(死亡,资源可以立与回收),而是进入Z状态。
我们不禁发出疑问,为什么??
既然如此,那么父进程派生的子进程,任务执行完了吗??执行的对不对??中途有无被操作系统杀掉??这些情况都需要告知父进程或者操作系统。
衍生问题:
以后再说…
如果创建子进程,子进程退出了,父进程不退出,也不等待子进程,子进程退出之后所处的状态就是Z状态。(ps:所谓等待子进程就是回收)
#include
#include
#include
int main()
{
pid_t id = fork();
if(id == 0)
{
//child
int cnt = 5;
while(cnt)
{
printf("我是子进程,我剩下 %d S\n", cnt);
cnt--;
sleep(1);
}
printf("我是子进程,我已经僵尸了,等待被检测\n");
exit(0);
}
else
{
while(1)
{
sleep(1);
}
}
return 0;
}
子进程五秒后退出,父进程一直在跑,也不回收子进程。我们代码没有写回收,所以子进程就是没有回收。
写一个可以在屏幕上循环打印出进程状态的脚本:
什么情况会一直僵尸?
一般必须要求父进程进行回收,后面说~
只需要将之前的代码稍微改一点:
#include
#include
#include
int main()
{
pid_t id = fork();
if(id == 0)
{
//child
while(1)
{
printf("我是子进程\n");
sleep(1);
}
printf("我是子进程,我已经僵尸了,等待被检测\n");
}
else
{
int cnt = 3;
while(cnt)
{
printf("我是父进程,我:%d\n", cnt--);
sleep(1);
}
exit(0);
}
return 0;
}
子进程一直不退,父进程3秒后退出。
再用之前的方式来观察。
我们突然发现,父进程3秒后消失不见了,然后原来的S+
变成了S
。
+
的,代表这个进程是个前台进程能在键盘ctr/ + C
的是前台进程后台进程ctr/ + C
干不掉。+
,它是个后台进程。只能通过命令来杀,这个后台进程被杀掉之后操作系统就将其回收了。
kill -9 进程的pid
我们还发现子进程的pid变成了1:
T
,一个是t
。我们先来将暂停状态T
:
我们先来写一段死循环打印:
#include
#include
int main()
{
while(1)
{
printf("Hello World!\n");
sleep(1);
}
return 0;
}
它肯定是S状态。
我们上述用kill -9 进程ID
的方式来杀掉进程,还有kill
其他的选项:
我们通kill -19 进程ID
就将这个进程暂停掉了:
我们可以用kill -18 进程ID
再将进程恢复启动起来。
补充:
我们再次回头看那张进程状态关系图,此时理解就不难了,随他怎么变,咱都能认得!!