进程的基本概念
- 课本概念:程序的一个执行实例,正在执行的程序等。
- 核心观点:担当分配系统资源(cpu时间、内存)的实体。
资源占用
它占用系统资源向CPU时间,内存等不同进程的资源是相互隔离的,确保进程的稳定运行。
结构组成
包含代码段存放程序代码,数据段存放全局变量的堆栈段,用于函数调用,系局部变量存储等部分。
父子关系
进程之间存在父子关系,父进程可以创建子进程,子进程可以继承父进程的某些属性,如文件描述符等。
- 在Linux中,进程是程序执行时的一个实例。
- 操作系统本质是一种进行软硬件资源管理的软件。
- 操作系统对上以人为本。
- 操作系统目的要对上给用户提供一个稳定的,高效的,安全的运行环境。
- 而采用对下进行软硬件资源管理的手段来保证资源是稳定的,高效的,安全的,能进行良好的工作。
操作系统进行内存管理、文件管理、进程管理和驱动管理。
而对于进程管理,在进程运行的过程中,从进程加载,进程运行到进程结束的整个生命周期里,操作系统要对进程进行管理。
那么操作系统如何对进程进行管理?
先描述再组织。
也就是说操作系统为了管理进程,给进程创建了一个数据结构,用来管理每一个不同进程。其中这个数据结构中包含各种该进程的属性。便于操作系统进行管理不同的进程。
进程 = 内核数据结构struct(自己对应的页表和虚拟地址空间之间的映射)+ 程序的代码和数据
描述进程PCB
进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。属于内核数据结构。课本上称之为PCB (process control block)。linux操作系统下PCB是task_struct。
- Task_struct是PCB的其中一种。
- 在Linux中描述进程的结构体叫做task_struct。
- taskStruct是Linux内核数据内核的一种数据结构,它会被加载到RAM。内存里并且包含进程的信息。
- Task struct是纯内存及数据结构,关机之后启动的内容和上一次不同,不是磁盘级别的不会被永久保存下来。
task_struct内容
- 标识符:描述本进程的唯一标识符,用来区别其他进程。
- 状态:任务状态,退出代码,退出信息等。
- 优先级:相对于其他进程的优先级
- 程序计数器eip:程序中即将被执行的下一条指令的地址。
- 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的代码块的内存。
- 上下文数据:进程执行时处理器的寄存器中的数据。
- Io状态信息:包括显示io请求分配给进程的io设备和被进程使用的文件列表。
- 记账信息:可能包括处理器时间总和,使用的时钟数分总和和时间限制记账号的。
- 其他信息。
通过系统调用获取进程的标识符
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
printf("pid: %d\n", getpid());
printf("ppid: %d\n", getppid());
return 0;
}
- 进程ID:pid
- 父进程ID:ppid
查看进程,创建子进程
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int ret = fork();
printf("hello proc : %d!, ret: %d\n", getpid(), ret);
sleep(1);
return 0;
}
在fork函数之后,两个进程是父子关系,一般而言,代码共享,但是数据是各自私有一份的,采用写时拷贝。
#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;
}
创建多进程multprocess
#include <stdio.h>
#include <iostream>
#include <unistd.h>
using namespace std;
#include <vector>
//创建十个子进程
int countNum = 10;
void SubProcess()
{
while(true)
{
cout << "I am child process , pid : " << getpid() << " , " << getppid() << endl;
sleep(5);
}
}
int main()
{
vector<pid_t> allchild;
for(int i = 0 ; i < countNum ; i++)
{
pid_t id = fork();//创建子进程
if(id == 0)
{
//子进程
SubProcess();
}
//父进程
// else if(id > 0)
//{
allchild.push_back(id);
//}
// else{
// perror("fork");
// return 1;
// }
}
cout << "我所有的孩子是:";
for(pid_t e : allchild)
{
cout << e << " ";
}
cout << endl;
sleep(10);
while(true)
{
cout << "I am father process , pid : " << getpid() << " , " << getppid() << endl;
sleep(10);
}
return 0;
}
一个函数fork怎么会有两个返回值?
- Fork函数是一个系统调用的函数,是操作系统用来创建子进程的一个函数。
- 在创建子进程之后,fork函数之后父子进程的核心工作都做完了,并且父子进程都已经运行了。
- Folk一定有很多执行fork函数return的时候,前面的核心工作代码已经执行完了。
- 也就是说当前子进程已经被创建了。
- 子进程被创建的时候,他的代码,数据结构以及taskstruct都源自于父进程的代码和数据以及父进程的task struct。父子的数据独立。
- 所以父子进程函数fork之后的返回值不同。
- 对于父进程fork函数返回新创建子进程的标识符pid,这个pid是一个大于0的值,附近层可以通过这个pid来对此进程进行管理,如监视子进程的状态,向子进程发出信号等。
- 对于子进程fork函数返回零是为了让子进程能够区别自己是子进程的身份,因为子进程可以通过这个返回值知道自己是新创建的子进程从而执行相应的代码逻辑比如执行与父进程不同的任务。
进程状态
看看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 */
"Z (zombie)", /* 32 */
};
R运行状态
并不意味着进程一定在运行中,它表示进程要么在运行中,要么在运行队列里面。
S休眠状态
意味着进程在等待事件完成,这里的睡眠有时候也可以被叫做可中断睡眠。
D磁盘休眠状态Disk sleep
有时候也叫不可中断睡眠状态,在这个状态的进程通常会等待io的结束。
T的状态Stopped
通过发送Sigstop信号来给进程来停止进程。这个可以被暂停的进程,可以通过发送sig cont信号让进程继续运行。
死亡状态dead
这个状态只是一个返回状态,你不会在任务列表里面看到。这个状态。
特殊的进程状态
僵尸进程
僵尸进程的概念。
- 僵尸进程是一个比较特殊的状态。当子进程退出并且副进程没有读取到子进程退出的返回代码时,子进程就属于僵尸进程。
- 僵尸进程会议终止状态保持在进程表中,并且会一直等待父进程读取退出状态代码。
- 所以只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态。
僵尸进程的代码实例
#include <stdio.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if(id < 0)
{
perror("fork");
return 1;
}
else if(id > 0)
{
//parent
printf("parent[%d] is sleeping...\n", getpid());
sleep(30);
}
else
{
printf("child[%d] is begin Z...\n", getpid());
sleep(5);
exit(EXIT_SUCCESS);
}
return 0;
}
僵尸进程的危害
- 进程退出状态必须被维持下去,因为他要告诉关心他的进程(父进程)。你交给我的任务我办的怎么样了?可是父进程如果一直不读取,那么子进程就一直处于z状态。
- 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task struct PCB中,换句话说。Z状态一直不退出,PCB一直都要被维护。
- 那一个父进程创建了很多子进程,就是不回收就会造成内存资源的浪费,因为数据结构对象本身就是要占用内存。会在内存中进行开辟空间抑制持续占用资源,最终导致内存泄露。
孤儿进程
孤儿进程的概念
- 父进程先退出,子进程则就称为孤儿进程。孤儿进程被1号进程领养,被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)
{
//child
printf("I am child, pid : %d\n", getpid());
sleep(10);
}
else
{
//parent
printf("I am parent, pid: %d\n", getpid());
sleep(3);
exit(0);
}
return 0;
}
进程优先级
优先级概念
- CPU资源分配的先后顺序就是指进程的优先权。因为CPU的资源是有限的,在一个电脑🖥️里面可能只有一个或者多个CPU,但是进程有很多个。
- Linux操作系统,Windows操作系统的大部分民用级别的操作系统都是采用分时操作系统。分时操作系统保证调度任务绝对公平。
- 优先权高的进程有优先执行权力。配置进程优先权对于多任务环境的Linux很有用,可以改善系统性能。
- 还可以把进程运行到指定的CPU上,这样一来把不重要的进程安排到某个CPU可以大大改善系统整体性能。
查看优先级
- uid代表执行者的身份。
- pid代表这个进程的代号
- ppid表示这个进程是由哪一个进程发展延伸而来的,以及父进程的代号。
- pri代表这个进程可被执行的优先级,其数越小越早被执行。
- ni代表这个进程的nice值。
Pri && ni
新的pri = 旧的pri + nice值
- 当nice值为负值的时候,那么该程序将会优先级值将变小及其优先级会变高,则其越快被执行。
- 调整进程优先级,在Linux下就是调整进程的nice值。
- Nice的值取值范围是-20~19,一共是40个级别。
Pri vs ni
- 进程的nice值不是进程的优先级,进程的nice值会影响到进程优先级变化。
- Nice值是进程优先级的修正数据。
用top命令更改已经存在进程的nice值
进入top后按r👉🏻输入进程pid👉🏻输入nice值