Linux进程状态

1.fork初识(接上篇文章继续)

fork创建子进程成功后有两个返回值,给父进程返回子进程的pid,给子进程返回0,如果创建失败,则返回负数。
接下来我和大家一起来研究一下这个fork函数,它又叫分叉函数。先看一段C语言代码。

 1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 int main()
  5 {
  6   pid_t id = fork();
  7   if(id<0)
  8   {
  9     //创建失败
 10     perror("fork");
 11     return 1;
 12   }
 13   else if(id == 0)
 14   {
 15     //child process
 16     while(1)
 17     {
 18       printf("I am child process:pid %d,ppid %d\n",getpid(),getppid());
 19       sleep(1);
 20     }
 21   }
 22   else
 23   {
 24     //father process
 25     while(1)
 26     {
 27       printf("I am father process:pid %d,ppid %d\n",getpid(),getppid());                          
 28       sleep(1);
 29     }
 30   }
 31   return 0;
 32 }

if和else if有没有可能是同时执行的呢?一段程序可能两个while循环同时执行吗?

[jyf@VM-12-14-centos 进程]$ ./test
I am father process:pid 21439,ppid 21083
I am child process:pid 21441,ppid 21439
I am father process:pid 21439,ppid 21083
I am child process:pid 21441,ppid 21439
I am father process:pid 21439,ppid 21083
I am child process:pid 21441,ppid 21439
I am father process:pid 21439,ppid 21083
I am child process:pid 21441,ppid 21439
I am father process:pid 21439,ppid 21083
I am child process:pid 21441,ppid 21439
I am father process:pid 21439,ppid 21083
I am child process:pid 21441,ppid 21439
I am father process:pid 21439,ppid 21083
I am child process:pid 21441,ppid 21439
I am father process:pid 21439,ppid 21083
I am child process:pid 21441,ppid 21439
^C

[jyf@VM-12-14-centos 进程]$ ps axj|head -1 && ps axj | grep test|grep -v grep //打印头部,显示test进程,不显示grep这个进程
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
    1 28134 28134 28134 ?           -1 Ssl   1001   0:00 /home/jyf/.VimForCpp/nvim test2.c
21083 31461 31461 21083 pts/1    31461 S+    1001   0:00 ./test
31461 31462 31461 21083 pts/1    31461 S+    1001   0:00 ./test

一个实时检查进程脚本

[jyf@VM-12-14-centos 进程]$ while :; do ps axj | head -1 && ps axj | grep test | grep -v grep; sleep 1;echo "################################################################";done

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    ```
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
 通过运行结果我们发现,这里的if和else if是同时执行的,所以两个while循环也是在同时执行。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
为啥呢?因为fork创建子进程成功后会有两个不同的执行流,也就是有两个进程,fork之后,代码是父子共享的,id在父进程里面是子进程的pid,在进程中是0。

## 1.1深入探讨
一份C语言代码,其中同一个变量,会有不同的值???
首先我们先分析一下结果,为什么给子进程返回0,给父进程返回子进程的pid(感性的认识)?
举个例子,有一对夫妻生了3个儿子,他们是不是要给他们的儿子取个名字,为啥?便于管理呀,给父进程返回子进程的pid也是如此。父进程:子进程 = 1:n

```c
fork()
{
	//创建子进程的逻辑
    return id;
}

创建成功一个子进程后,操作系统会在内存中生成一个描述它的属性的task_struct结构体,它的内部属性,要以父进程为模板,当在fork函数内部子进程已经创建成功后,fork函数后面的代码都是父子进程所共享的,比如这里的return id,每个进程得到它们自己的id返回值,继续下面的代码执行。这就是为什么同一个变量,会有不同的值的原因(1.fork内部,父子进程会各自执行自己的return语句 2.返回两次,并不意味着会保存两次)。
有同学可能会问,那这两个进程谁先返回,谁后返回,谁先执行,出fork函数后之后的代码,其实这个与操作系统有关,没办法判断。
我们知道在CPU上面的进程调度,是在运行队列,也就是task_struct的队列中选择一个进程的过程。操作系统和CPU运行某一个进程,本质是从task_struct形成的队列中选择一个task_struct来执行它的代码。
起初父进程在CPU的运行队列里,相较于刚刚创建成功的子进程自然是排在它前面,但是这就代表一定是父进程先返回return吗?不一定的。CPU进程调度是按照某一算法走的,可能CPU在执行父进程的代码时,刚到return 的前面,发现调度的时间片到了,就被放到运行队列的尾部重写排队,此时子进程在调度的时候执行了return 语句,此时子进程就比父进程优先返回;当然也有可能父进程调度的时候就return 了,那就是父进程优先返回了;执行下面的代码同此理。
所以这取决于CPU和操作系统的调度器。

2.进程状态

2.1看看Linux内核源代码怎么说?

为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在Linux内核里,进程有时候也叫做任务)。
下面的状态在Kernel源代码里定义:

/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */   //暂停状态
"t (tracing stop)", /* 8 */  //调试状态
"X (dead)", /* 16 */    //此时进程已经寄了,但操作系统还没来得及回收,操作系统想提高效率一次性将X状态的进程都回收,注意次状态的瞬时性非常强,一般看不到的。不要那你的反应去与操作系统比,操作系统再慢也比人要快。
"Z (zombie)", /* 32 */ //僵尸进程
};
struct task_struct
{
	...
	int status;
	...
}

R运行状态(running):并不意味着进程一定在运行中,它表明进程要么在运行中要么在运行队列里。
S睡眠状态(sleeping):意味着进程在等待事件完成(这里的睡眠也叫做可中断睡眠(interruptible sleep))
D磁盘休眠状态(Disk sleep)有时候也叫做不可中断睡眠状态(uninterruptible sleep),这个状态的进程通常会等待IO的结束。
这里大家可能会想,啥是可中断睡眠,啥是不可中断睡眠,当服务器压力过大时,OS会通过一定的手段,杀掉一些进程,来起到节省空间的作用,如果你的一个进程在进行IO操作,即在磁盘存取重要文件,此时在内存中的进程需要等待一个存取结果,是成功了还是失败了,此时这个进程是不能被杀掉的,将其设置成D状态即可;有些进程在内存不足时可以杀掉来节省空间,这种进程设置成S状态即可
T停止状态(stopped):可以通过发送SIGSTOP信号给进程来停止(T)进程,这个被暂停的进程可以通过发送SIGCONT信号让进程继续运行。
信号:

[jyf@VM-12-14-centos 进程]$ kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX
[jyf@VM-12-14-centos 进程]$ kill -19 15498

[2]+  Stopped                 ./mytest1
[jyf@VM-12-14-centos 进程]$ kill -18 15498

[2]+  Stopped                 ./mytest1

X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。

2.2Linux操作系统进程的状态

新建:字面意思,即有了它所对应的进程,创建了其task_struct结构体。
运行:task_struct结构体在运行队列中排队,就叫做运行态。
在这里插入图片描述
阻塞:等待非CPU资源就绪,阻塞状态
比如:

int main()
{
	int a = 0;
	scanf("%d",&a);
	return 0;
}

当运行此程序时,它在等待键盘数据就绪,也就是非CPU资源就绪,此时此进程就处于阻塞状态。
挂起:当内存不足的时候,OS通过适当的置换进程的代码和数据到磁盘,此时进程的状态就叫做挂起!
在这里插入图片描述

2.3Linux具体状态的演示

2.3.1运行态

  1 #include <stdio.h>
  2 int main()
  3 {
  4   while(1)
  5   {
  6                                                                                                                                                                                                              
  7   }
  8   return 0;
  9 }

 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
31710 32728 32728 31710 pts/1    32728 R+    1001   0:08 ./mytest1
##########################################################

STAT是R+,可以看出此进程是运行态。
R运行状态(running):并不意味着进程一定在运行中,它表明进程要么在运行中要么在运行队列里。

2.3.2 阻塞态

  1 #include <stdio.h>
  2 int main()
  3 {
  4   int a = 0;
  5   scanf("%d",&a);                                                                                 
  6   while(1)
  7   {
  8  
  9   }
 10   return 0;
 11 }
~     

##########################################################
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
31710  1079  1079 31710 pts/1     1079 S+    1001   0:00 ./mytest1

STAT为S+,可以看出此进程是阻塞状态,正在等待非CPU资源就绪。
S睡眠状态(sleeping):意味着进程在等待事件完成(这里的睡眠也叫做可中断睡眠(interruptible sleep))

2.3.3 R+,S+等的‘+’号是什么意思?

这里的+号表示此进程是一个前台进程,当它在运行的时候,此时再在bash上输入命令如ls,pwd等会发现无效,此时此进程占用了整个bash。
要想此进程在运行时也能使用bash命令行解释器,此时要将此进程变成后台进程,只需在运行进程的时候加个&即可

[jyf@VM-12-14-centos 进程]$ ./mytest1 &
[1] 5168

2.3.4 Z(zombie)-僵尸进程

僵尸进程:一个进程已经退出,但是还不允许被OS释放,处于一个被检测的状态,这个状态叫做僵尸状态。
维持改状态,为了让父进程和OS来进行回收。
僵尸状态是一个比较特殊的状态。当进程退出并且父进程(使用wait()调用)没有读取到子进程退出的返回代码时就会产生僵尸进程。
僵尸进程会以终止状态保持在进程表中,并且一直等待父进程读取退出状态代码。
所以,只要子进程退出,父进程还在运行,但父进程还没有读取子进程状态,子进程进入Z状态。
创建一个僵尸进程:

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>                                                                               
  4 int main()
  5 {
  6   pid_t id = fork();
  7 
  8   if(id<0)
  9   {
 10     perror("fork");
 11     return 1;
 12   }
 13   else if(id == 0)
 14   {
 15     //child process
 16     while(1)
 17     {
 18       printf("I am child,pid:%d,ppid:%d\n",getpid(),getppid());
 19       sleep(1);
 20       break;
 21     }
 22     exit(0);
 23   }
 24   else
 25   {
 26     //parent process   
 27     while(1)
 28     {
 29       printf("I am parent,pid:%d,ppid:%d\n",getpid(),getppid());
 30       sleep(1);
 31     }
 32   }
 33   return 0;
 34 }

编译并在另一个终端启动监控

----------------------------------------------------------
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
 9996 10784 10784  9996 pts/1    10784 S+    1001   0:00 ./mytest2
10784 10785 10784  9996 pts/1    10784 Z+    1001   0:00 [mytest2] <defunct>不在存在的
----------------------------------------------------------

可以看到此时的进程状态是Z+状态。

僵尸进程危害
进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的。
维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护?是的。
说明僵尸进程的代码和数据可以回收,但维护其状态信息的PCB结构体没被回收。
那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的。因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间。
内存泄漏?是的!!!
如何避免,后面会说。

有些状态目前不太好演示,在此之前已经介绍清楚了,不理解的地方可以私信问我,有问必回嗷。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值