进程
进程基本概念
1.什么是进程
程序:是静态的,就是个存放在磁盘里的可执行文件,如: Tim.exe
进程:是动态的,是程序的一次执行过程,如:可同时启动多次Tim程序
同一个程序多次执行会对应多个进程
进程的概念有两个层面上的解释:
教科书上的解释:进程是程序的一个执行实例,是正在执行的程序。
内核上的观点:担当分配系统资源(cpu 时间,内存)的实体。
进程控制块(PCB)
进程的信息会被放到一个叫进程控制块的数据结构中,可以理解成它是进程属性的集合。linux中的PCB实际上是一个叫做task_struct的结构体
task_struct 结构体
task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息
task_struct中的内容
PID标示符: 描述本进程的唯一标示符,用来区别其他进程。
状态: 任务状态,退出代码,退出信号等。
优先级: 相对于其他进程的优先级。
PC程序计数器: 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
I/ O状态信息: 包括显示的I/O请求,分配给进程的I/ O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
其他信息
//截取源码部分
struct task_struct {
volatile long state;//状态 /* -1 unrunnable, 0 runnable, >0 stopped */
void *stack;//栈
atomic_t usage;
unsigned int flags; /* per process flags, defined below */
unsigned int ptrace;
int lock_depth; /* BKL lock depth */
/* task state */
int exit_state;
int exit_code, exit_signal;
int pdeath_signal; /* The signal sent when the parent dies */
/* ??? */
unsigned int personality;
unsigned did_exec:1;
unsigned in_execve:1; /* Tell the LSMs that the process is doing an
* execve */
unsigned in_iowait:1;
/* Revert to default priority/policy when forking */
unsigned sched_reset_on_fork:1;
pid_t pid;//标识符
pid_t tgid;
//...
};
同时挂三个QQ号,会对应三个QQ进程,它们的PCB、数据段各不相同,但程序段的内容都是相同的(都是运行着相同的QQ程序)
进程是如何被操作系统管理起来的
六字真言:“先描述,再组织”
操作系统上跑的是一个个程序,也就是进程,那么操作系统就是要管理这些进程,怎么管理呢?
进程有它的对应的属性(下面会有介绍),因为linux操作系统是用c语言写的,所以进程的属性是用结构体来描述的(先描述),这些结构体会被一个双链表链接起来,所以对进程的管理也就演变成了对数据结构的管理。
先描述
将进程用一个数据结构描述,linux中用task_struct 结构体来描述,将进程的所有属性都包含在这个结构体中(里面会有指针指向进程的代码和数据)。一个进程的组成就是其task_struct 结构体和其对应的代码和数据。
再组织
将一个个进程的数据结构组织在一起,linux中使用双链表将这些结构体一个个连起来,完成对进程数据结构的组织。
描述好,组织好,才好管理
管理进程就是变成了对数据结构的管理,管理好这些数据结构就可以管理好进程!!!
进程在内存中的样子:
如何将进程运行起来:
进程的分类
在Linux系统中,根据进程的特点,把进程可以分为三类:交互进程、批处理进程和守护进程。
1. 交互进程:是由shell启动的进程,它既可以在前台运行,也可以在后台运行。交互进程在执行过程中,要求与用户进行交互操作。简单来说就是用户需要给出某些参数或者信息,进程才能继续执行。
默认在前台运行
前台进程可以从终端输入也可以从终端输出,后台进程不可以从终端读取输入,但是可以从终端输出
全面理解前台进程,后台进程的概念,以及之间如何切换,init进程与系列文章大总结(系列文章第四篇)_进程前台运行和后台运行-CSDN博客
2.批处理进程:和在终端无关,被提交到一个作业队列中以便顺序执行,与windows原来的批处理很类似,是一个进程序列。该进程负责按照顺序启动其它进程。
3.守护进程:和终端无关,一直在后台运行。
是执行特定功能或者执行系统相关任务的后台进程。守护进程只是一个特殊的进程,不是内核的组成部分。许多守护进程在系统启动时启动,直到系统关闭时才停止运行。而某些守护进程只是在需要时才会启动,比如FTP或者Apache服务等,可以在需要的时候才启动该服务。
另外,根据进程状态的不同,又可以把进程分为另外三类:守护进程、孤儿进程和僵尸进程。
1.守护进程:(补充):所有守护进程都可以超级用户(用户ID为0)的优先权运行;守护进程没有控制终端;守护进程的父进程都是init进程(即1号进程)。
但是,并非所有在后台运行的进程都是守护进程,因为我们可以使用符号“&”来使进程在后台运行。比如:./bin/process_test &,执行该条命令后,相应的进程在后台运行。
2.孤儿进程:一个父进程退出后,它的一个或多个子进程还在运行,那么这些子进程将成为孤儿进程。孤儿进程将被init进程所收养,并由init进程对它们完成状态收集工作。
3.僵尸进程:一个子进程结束但是没有完全释放内存(在内核中的 task_struct没有释放),该进程就成为僵尸进程。
当僵尸进程的父进程结束后该僵尸进程就会被init进程所收养,最终被回收。
僵尸进程会导致资源的浪费,而孤儿进程不会。
-------------------------------------------------------------
子进程成为孤儿进程
...
main(void)
{
pid_t pid;
pid = fork();
if(pid < 0)
{
perror("fork error\n");
exit(1);
}
else if(pid > 0)
{
// parent process
printf("parent process dead\n");
exit(0);
}
else
{
// child process
sleep(3)
printf("I'm an orphen\n");
printf("pid: %d, ppid: %d\n", getpid(), getppid());
}
}
---------------------------------------------------
子进程成为僵尸进程
...
main(void)
{
pid_t pid;
pid = fork();
if(pid < 0)
{
perror("fork error\n");
exit(1);
}
else if(pid == 0)
{
// child process
printf("I'm a zombie process\n");
printf("pid: %d, ppid: %d\n", getpid(), getppid());
exit(0);
}
else
{
// parent process
printf("pid: %d, ppid: %d\n", getpid(), getppid());
}
}
进程的状态
创建态: 进程正在被创建时,它的即态是“创建态”,在这个阶段操作系统会为进程分配资源、初始化PCB。
就绪态: 当进程创建即成后,便进入“就绪态”,处于就绪态的进程已经具备运行条件,但由于没有空闲CPU,就暂时不能运行。
运行态: 当CPU空闲时,操作系统就会选择一个就绪进程,让它上处理机运行。此时该进程就由就绪态变为运行态,CPU会执行该进程的指令序列。
阻塞态(等待态): 在进程运行的过程中,当能会请求等待某个事件的发生(如等待某种系统资源的分配,或者等待其他进程的响应)。在这个事件发生之前,进程无法继续往下执行,此时操作系统会让这个进程下CPU,并让它进入“阻塞态”
当CPU空闲时,又会选择另一个“就绪态”进程上CPU运行
终止态(死亡态): 一个进程当以执行exit系统调用,请求操作系统终止该进程。此时该进程会进入“终止态”,操作系统会让该进程下CPU,并回收内存空间等资源,最后还要回收该进程的PCB。当终止进程的工作完成之后,这个进程就彻底消失了。
注意:进程PCB中存放着标识当前进程状态的变量state,如1表示创建态、2表示就绪态、3表示执行态……