进程状态 | 僵尸进程 | 孤儿进程 | 前台后台进程 | 守护进程
文章目录
- 1.进程的三种基本状态
- 2.Linux中进程状态查看
- 2.1.进程检测脚本
- 2.2.各种状态查看
- 3.孤儿进程
- 4.前台、后台、守护进程
1.进程的三种基本状态
进程的在系统当中是走走停停的,「运行 - 暂停 - 运行」的活动规律;进程在活动期间的三种状态:运行状态、就绪状态、阻塞状态。
- 运行状态(Running):该时刻进程占用 CPU;
- 就绪状态(Ready):可运行,由于其他进程处于运行状态而暂时停止运行;
- 阻塞状态(Blocked):该进程正在等待某一事件发生(或等待非CPU资源,如等待输入/输出IO操作的完成)而暂时停止运行,这时,即使给它CPU控制权,它也无法运行;
PCB是描述进程信息的结构体,它是通过链表的方式组织的。如果处于就绪状态的进程链在一起的是运行队列;而处于阻塞状态的进程链在一起的是阻塞队列!在Linux中,就绪和运行态都是R状态。来看看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 */
"Z (zombie)", /* 32 */
};
2.Linux中进程状态查看
2.1.进程检测脚本
while :; do ps axj | head -1 && ps axj | grep mytest;sleep 1;echo "------------------";done
更加干净!
while :; do ps axj | head -1 && ps axj | grep mytest | grep -v grep;sleep 1;echo "------------------";done
2.2.各种状态查看
R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队里。测试代码:
#include <stdio.h>
#include <unistd.h>
int main()
{
while(true)
{
printf("hello world\n");
sleep(1);
}
return 0;
}
测试结果:R+后面的进程代表是一个前台进程
S睡眠状态(sleeping): 进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep)
#include<stdio.h>
int main()
{
int a = 0;
printf("请输入一个整数:");
scanf("%d",&a);
return 0;
}
测试结果:
D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束,不能被叫醒!
可以使用dd命令来,测试dd
命令在处理数据时非常强大,但同时也具有一定的风险性。如果使用不当,可能会导致数据丢失或损坏(建议是不用测试这个状态)
T停止状态(stopped): 可以通过发送 SIGSTOP (kill -19 pid)信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT(kill -18 pid) 信号让进程继续运行。**典型应用:调试!断点能停下来,是因为进程的T状态!**测试代码:
使用命令:kill -19 pid;将进程暂停
使用命令:kill -18 pid;将进程运行
#include <stdio.h>
#include <unistd.h>
int main()
{
while(true)
{
printf("hello world\n");
sleep(1);
}
return 0;
}
测试结果:
Z状态:僵尸(死)状态: 当子进程退出,但是父进程没调用wait
或waitpid
去获取子进程的状态信息,没有检测到子进程返回状态的代码时,子进程就会处于一个被检测的状态,就是僵尸状态。测试代码:
#include<stdio.h>
#include<unistd.h>
int main()
{
pid_t ret = fork();
if(ret < 0)
{
perror("fork faile\n");
return 1;
}
else if(ret == 0)
{
int cnt = 5;
while(cnt)
{
printf("I am child pid = %d\n",getpid());
cnt--;
sleep(1);
}
}
else
{
while(1)
{
printf("I am father pid = %d\n",getpid());
sleep(1);
}
}
return 0;
}
测试结果:
在这里插入图片描述
僵尸进程的危害:子进程被创建出来,是为了完成父进程交给它的任务,父进程需要读取子进程的返回状态,但是父进程先退出,那么子进程就要一直维护Z状态;系统要一直维护子进程的PCB,那么就会造成内存泄漏。
父进程通过调用wait
或 waitpid
进程等待的方式解决僵尸进程问题。
3.孤儿进程
孤儿进程:父进程结束了,而它的一个或多个子进程还在运行,那么这些子进程就成为孤儿进程(father died)。子进程的资源由init进程(进程号PID = 1)回收。
测试代码:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if (id < 0)
{
perror("fork");
return 1;
}
else if (id == 0)
{
printf("I am child, pid : %d\n", getpid());
sleep(10);
}
else
{
printf("I am parent, pid: %d\n", getpid());
sleep(3);
exit(0);
}
return 0;
}
测试结果:
4.前台、后台、守护进程
前台进程通常是用户正在交互的进程,本质是谁占有键盘资源谁就是前台进程!
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
while(true){
printf("hello world\n");
sleep(1);
}
return 0;
}
启动前台进程:./mytest 或输入指令
启动后台进程:./mytest &
,从下图可以看到我启动一个前台进程之后,通过键盘输入的 ll
和 cd
指令都不管用了。
启动后台进程:我们程序也在显示器上打印,但是输入的 ll
和 cd
指令可以执行。
在这里插入图片描述
前台进程可以使用信号的方式终止,将程序启动为后台进程发现 ctrl + c
发送信号不能终止进程!使用jobs
查看系统运行的任务 使用 fg + 任务号
将后台进程转成前台进程,再通过 ctrl + c
终止进程!
[xiyan@hecs-34711 ~]$ jobs
[1]+ Running ./mytest & (wd: ~/code/test)
[xiyan@hecs-34711 ~]$ fg 1
任务和进程的关系(一个任务,可以一个人完成,也可多个人完成!)
一个任务可以是一个进程执行也可以是多个进程执行,多个进程组成进程组以第一个进程的进程pid为任务号,一个进程可以自成进程组!通过查看PGID属性字段就能验证。
[xiyan@hecs-34711 test]$ sleep 1000 | sleep 2000 | sleep 3000 &
[1] 6114
[xiyan@hecs-34711 test]$ mytest >> log.txt &
[2] 6141
[xiyan@hecs-34711 test]$ jobs
[1]- Running sleep 1000 | sleep 2000 | sleep 3000 &
[2]+ Running ./mytest >> log.txt &
[xiyan@hecs-34711 test]$ ps axj | head -1 && ps xj | grep 'sleep'
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
4559 6112 6112 4559 pts/4 6267 S 1000 0:00 sleep 1000
4559 6113 6112 4559 pts/4 6267 S 1000 0:00 sleep 2000
4559 6114 6112 4559 pts/4 6267 S 1000 0:00 sleep 3000
[xiyan@hecs-34711 test]$ ps axj | head -1 && ps xj | grep 'mytest'
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
4559 6141 6141 4559 pts/4 6285 S 1000 0:00 ./mytest
会话的概念
Linux操作系统中,当一个用户登录的时候会分配一个会话,一个会话期间可以创建多个进程组,退出登录,会话会销毁;会话中前台进程终止,后台进程可能不会终止,但是会受一定的影响。
一个会话有多个进程,但是前台进程只有一个,系统会让bash
来,充当前台进程,如果启动一个前台进程,就会将bash
替换,当我们自己启动的前台进程退出,又将bash
换上来!
退出登录后再登录 , 我的Linux机器是直接就终止了我们程序!如何解决——进程守护化!
[xiyan@hecs-34711 test]$ ./mytest >> log.txt &
[1] 12130
[xiyan@hecs-34711 test]$ jobs
[1]+ Running ./mytest >> log.txt &
[xiyan@hecs-34711 test]$ ps axj | head -1 && ps axj | grep 'mytest'
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
12035 12130 12130 12035 pts/3 12153 S 1000 0:00 ./mytest
12035 12154 12153 12035 pts/3 12153 R+ 1000 0:00 grep --color=auto mytest
[xiyan@hecs-34711 ~]$ ps axj | head -1 && ps axj | grep 'mytest'
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
12181 12214 12213 12181 pts/3 12213 R+ 1000 0:00 grep --color=auto mytest
什么是守护进程
将自成进程组自成会话的进程,称为守护进程,他的本质也是孤儿进程;
让会话1创建一个守护进程,当会话1退出守护进程不受影响!
[xiyan@hecs-34711 ~]$ man 2 setsid
[xiyan@hecs-34711 ~]$ man daemon # 系统自带的方法
参考代码:
#pragma once
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <signal.h>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
const std::string nullfile = "/dev/null";
void Daemon(const std::string &cwd = "")
{
// 1. 忽略其他异常信号
signal(SIGCLD, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
signal(SIGSTOP, SIG_IGN);
// 2. 将自己变成独立的会话
if (fork() > 0)
exit(0);
setsid();
// 3. 更改当前调用进程的工作目录
if (!cwd.empty())
chdir(cwd.c_str());
// 4. 标准输入,标准输出,标准错误重定向至/dev/null
int fd = open(nullfile.c_str(), O_RDWR);
if(fd > 0)
{
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
close(fd);
}
}