进程概念
进程是什么:
- 进程是系统进行资源分配和调度的基本单位.
- 简单来理解进程就是系统中正在运行的程序;
详细来说进程是操作系统对一个运行中程序的描述,通过这个描述信息,实现对程序的调度运行;[例如: 通过描述信息中的内存指针能够找到内存中运行的程序代码及数据,并且通过上下文数据可以保存程序切换调度时正在处理的数据, 以及通过程序计数器保存进程切换时即将执行的下一步命令等等…]
操作系统对进程的控制是通过一个数据结构 PCB(进程控制块 Process Control Block) 来实现的.在Linux下这个保存描述进程信息的数据结构叫做task_struct
task_struct 中包含以下元素:
- 标识符: 描述一个进程的唯一标识符,用来区别其它进程
- 状态: 描述进程的执行状态(运行/阻塞等)
- 优先级: 一个进程对于CPU资源获取的优先权----权级用数字表示
进程的优先级决定了cpu调度优先权的级别(设置进程优先级的目的是为了让操作系统运行的更加良好,进而提升用户体验)
- 程序计数器: 进程下一步执行的指令的地址
- 内存指针: 指向程序代码的指针
- 上下文数据: 指令执行时寄存器中的数据
- IO状态信息: I/O使用请求等
- 记账信息: 处理器的时间总和
- 其它信息
深入了解请参考 task_struct 源码
进程信息的查看
常用的进程查看指令:
- ps -ef: 查看所有的进程信息
- ps -aux: 查看内存中的进程信息
- ps -l: 列出与本次登录有关的进程详细信息
ps 指令查看的是进程信息的一个快照,显示的我们执行ps这个命令时进程的信息
top 指令显示的是进程的动态信息,使用这个命令会看到进程信息的动态变化。
ps指令 常见选项
a 显示现行终端机下的所有程序,包括其他用户的程序。
-A 显示所有程序。
c 列出程序时,显示每个程序真正的指令名称,而不包含路径,参数或常驻服务的标示。
-e 此参数的效果和指定"A"参数相同。
e 列出程序时,显示每个程序所使用的环境变量。
f 用ASCII字符显示树状结构,表达程序间的相互关系。
-H 显示树状结构,表示程序间的相互关系。
-N 显示所有的程序,除了执行ps指令终端机下的程序之外。
s 采用程序信号的格式显示程序状况。
S 列出程序时,包括已中断的子程序资料。
-t <终端机编号> 指定终端机编号,并列出属于该终端机的程序的状况。
u 以用户为主的格式来显示程序状况。
x 显示所有程序,不以终端机来区分。
常用ps -aux 指令 在结合管道 | 加 grep指令过滤查找指定的进程
进程的创建
在Linux系统中,系统启动后的第一个进程由系统来创建, 叫做根进程.其余的进程都由已经存在的进程来创建,由已创建的进程创建的新进程叫做子进程,而创建子进程的进程叫作父进程,具有同一个父进程的进程叫做兄弟进程.
进程创建过程:
子进程的创建
在Linux中, 子进程是由父进程以分裂的方式创建的, 创建一个子进程的系统调用为 fork()
由上文可知:系统是通过PCB来表示进程的,所以创建新进程就是要创建一个新的PCB,而创建一个新的PCB最简单的方法就是复制.但是由于进程的特性,导致不能完全复制,比如每个进程都有唯一的标识符,父子进程的代码共享,数据区和栈区独有.
在Linux下 通过man fork 来认识fork
fork(): 创建子进程(通过复制的方式创建了一个新的进程)
fork 有三种返回值:
- 小于 0 表示创建子进程失败
- 等于 0 表示是子进程
- 大于0 (其实是子进程的pid) 表示是父进程
父子进程 代码共享,数据独有(数据是各自开辟空间保存,使用写时拷贝技术)
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int ret = fork();
if(ret < 0){
perror("fork");
return 1;
}
else if(ret == 0){ //child
printf("I am child : %d!, ret: %d\n", getpid(), ret);
}else{ //father
printf("I am father : %d!, ret: %d\n", getpid(), ret);
}
sleep(1);
return 0;
}
进程的状态
一个进程可以有多个状态,下面是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 */
};
- R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
- S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。
- D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
- T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
- X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
僵尸进程
处于僵死状态的进程(进程已经退出, 但是资源没有完全释放)
僵尸进程不可恢复
僵尸进程的产生原因:
子进程先于父进程退出 一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程(也就是进程为中止状态,僵死状态)。
僵尸进程的危害:
资源泄露(一个用户所能创建的进程数量是有限的, 并且资源没有完全回收会占据内存资源)
僵尸进程的避免:
- 进程等待
- 退出父进程(父进程退出后,子进程的退出原因就没有必要在保存了,因此也就被释放掉了)
僵尸进程演示:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
pid_t pid = fork();
if(pid < 0)
{
perror("fork error");
return -1;
}
else if(pid == 0)
{
printf("child[%d] is begin Z...\n", getpid());
sleep(5);//为了方便我们观察子进程退出之前的状态
exit(0);
}
else
{
sleep(30);//为了使子进程先于父进程退出
printf("parent[%d] is sleeping...\n", getpid());
}
while(1)
{
sleep(1);
}
return 0;
}
父子进程都未退出时
子进程退出,父进程未退出.子进程变为僵尸进程
孤儿进程
一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作;
特性:
- 孤儿进程运行在系统后台
- 孤儿进程是不会成为僵尸进程的, 因为一号进程随时关注子进程退出.
孤儿进程演示:
[#include<stdio.h>
#include<unistd.h>
int main()
{
pid_t pid = fork();
if(pid < 0)
{
return -1;
}
else if(pid == 0)
{
sleep(10);//父进程先退出
printf("this is child\n");
}
else
{
sleep(5);
printf("this is parent\n");
}
return 0;
}](https://img-blog.csdnimg.cn/20200908224254331.png#pic_center)
父子进程都未退出:
父进程退出, 子进程未退出,成为孤儿进程,被一号进程收养