进程
- 1.什么是进程
- 2.创建进程
- 2.1进程标识符
- 2.2初时fork()函数,创建进程
- 3.进程状态
- 3.1进程状态的描述
- 3.2Linux中具体的进程状态
- 4 僵尸状态
- 5 孤儿进程
- 6进程优先级
1.什么是进程
进程在我们的电脑和手机上是无处不在的。例如我们windows系统下的任务管理器
这一个个的启动项就是我们的进程。我们在这个图片上可以看出,进程是需要用到CPU的资源的。当我们开启一个可执行程序,那么这个可执行文件就被加载到内存中,这个可执行程序就是进程。那么在我们的CPU有那么多的进程,CPU是怎么把他们区分并执行起来呢?
我们刚刚提到CPU去管理进程,这个说法是有些不对的,实际上,真正管理进程的软件是操作系统操作系统就是我们常常说的例如:windows,macOS,linux;
操作系统管理进程的关键就是先描述在组织我们都知道C语言的结构体,他可以描述对象的不同属性,所以操作系统的描述就是在创建结构体,一个对象对应一个结构体。**进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。**我们叫他PCB,Linux的PCB是task_struct。他囊括了进程的信息。
task_struct的内容分类
- 标示符: 描述本进程的唯一标示符,用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器: 程序中即将被执行的下一条指令的地址
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
- I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等
- …
总结进程可以简单的用一个图来表示
进程=内核数据结构+可执行程序
2.创建进程
2.1进程标识符
在linux中一个进程会有一个父进程,我们可以获取进程的id
- 进程id(pid)
- 父进程id(ppid)
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
printf("before fork: I am a process,pid:%d,ppid:%d\n",getpid(),getppid());
fork();
printf("after fork: I am a process,pid:%d,ppid:%d\n",getpid(),getppid());
sleep(2);
return 0;
}
这里说明了fork之后创建了两个进程
2.2初时fork()函数,创建进程
父子进程代码共享,数据各自开辟空间存放,私有一份,也就是写实拷贝。
子进程被创建,以父进程为模板
接下来我们重点谈一谈fork的返回值
子进程的pid返回给父进程
0返回给子进程
父子进程做不同的工作,两者可以同时运行
为什么是这么返回的呢?我们后面会提到僵尸进程,我们现在只需要知道,父进程是需要找到子进程的,那么子进程的pid返回给父进程就是一种标识。
另一个问题为什么同一个变量既能等于0又大于0?
可以用一个变量,表示不同的内存
前面提到子进程被创建,以父进程为模板,他们共享一份数据,那如果我们对子进程进行修改的话,会影响父进程的数据吗?
答案是不会因为进程之间是具有独立性的当我们的子进程想要修改数据的时候,这时就会发生写实拷贝,给子进程提供一个新空间,空间内的数据都是上面内容的复制。
(注:写实拷贝我们后面的进程控制还会进一步分析)
3.进程状态
3.1进程状态的描述
状态就是一个整型变量,在task_struct的一个整型变量
状态决定了进程的后续动作。
/*
- 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 (Sleep):该进程目前正在睡眠状态,但可以被唤醒。
- D:不可被唤醒的睡眠状态,通常这个进程可能在等待I/O的情况。
- T :停止状态,发送一个暂停信号给进程,进程就暂停了。
- t:追踪停止状态,通常在断点调试时,进程处于此状态。
- X :死亡状态,这个状态是用来告诉操作系统的,所以我们观察不到此状态。
- Z (Zombie):僵尸状态,进程已经死亡,但是却无法被删除至内存外。
在linux中我们可以用指令查看进程的状态
ps aux / ps axj
3.2Linux中具体的进程状态
我们可以写一段死循环代码来让一个进程一直运行方便我们观察
我们可以用这个指令让我们更方便看
ps ajx | head -1 && ps ajx | grep test
4 僵尸状态
僵尸状态:进程已经退出,当前进程的状体还需要自己维持,供上层读取,
也就是代码已经被释放了,但是进程的PCB还在,执行完程序的数据还没有被上层(父进程)获取相关数据。
没有读取的危害
僵尸状态的数据如果一直没有读取,那么他就会一直存在,他的PCB会一直占取着内存,导致内存泄漏。
为什么要有这样的状态呢?
我们前面提到,创建进程就是为了完成某种工作的,我们要知道他有没有完成工作,就要读取他PCB里面的数据结果。
5 孤儿进程
字面意思:父进程先退出了,留下的子进程就变成了孤儿。
当父进程衍生出一个或多个子进程后,父进程先退出了,只留下了衍生出的一个或多个子进程,这些子进程就叫孤儿进程。
但是没有父进程那子进程怎么返回呢?
我们来看看这个子进程的ppid变成了什么?
这里操作系统会让1号进程(init)来领养这个子进程,当然这个子进程也就由init返回了。这里1号进程也就是操作系统。
那如果操作系统在这个不做这个优化会怎么样呢?
和僵尸进程类似的,子进程无法返回就会一直存在着,这时就会造成内存泄漏。
6进程优先级
这几年,人们越来越喜欢旅游,特别是在节假日的时候,人是非常多的,但是地方就那么大,所以不可避免的我们需要排队;这里我们就可以类比操作系统了,内存就只有那么大,他能运行的进程是有限度的,所以进程也需要排队,这就是我们进程排队。
==进程优先级的前提:==进程要访问某种资源,通过排队,确认先后顺序。
我们来看看进程的优先级,打印出所有的优先级
ps -la
这里的PRI就是我们所说的优先级
linux中的默认优先级是80,可以被修改,范围是【60,99】,一共有40个优先级。
Linux优先级就是数字,数字越小优先级更高;
我们可以手动的改变优先级
top
- r
- 输入要改变进程的pid
- 输入NI值
我们不能直接改变pri,而是改变nice值;
pri=pri(old)+nice
如果我们的nice过大超过了pri的范围,那么系统会自动调整成范围之内。