Linux进程的概念
一:进程
1.1:进程的概念
-
课本概念:程序的一个执行实例,正在执行的程序等
-
内核观点:担当分配系统资源(CPU时间,内存)的实体
1.2:进程查看(PID进程的身份标识)
小黑:查看进程的命令总结如下
- ps 相当于任务管理器,能够查看系统都有哪些进程
- ps aux 查看所有进程
- ps aux | less 配合less 对查看结果进行翻页和其他操作
- top 也可查看进程
- ps aux | grep [进程名] 查看匹配的进程
- 该命令下会捕捉到两个进程信息:1.你所查找的程序2.你敲下的命令进程
- 敲下的命令在执行过程中也是一个进程
- Ctrl + z 将一个程序放到后台,但是不退出程序(程序处于未执行状态)
- 按下 fg 将后台的程序调到前台继续执行
- Ctrl + c 关闭一个正在执行的进程
1.3:进程的管理
小辉:小黑,这么多进程怎么管理啊?
小黑:进程的管理是通过PCB来完成的
1.描述进程(PCB进程控制块 --> 就是一个结构体(task_struct))
进程信息被放在一个叫做进程控制块的数据结构中
-
进程标识符(pid)
-
内存指针(告诉进程代码/数据都在内存的哪个部分)
-
以结构体(task_struct)的形式存在于内核中
PCB:针对通常情况
2.task_struct:(PCB的一种,)只针对Linux
- 在Linux中描述进程的结构体叫做 task_struct
- task_struct 是Linux内核的一种数据结构,它会被装载到RMB(内存里并且包含着进程的信息)
3.task_struck内容分类
- 1.标示符: 描述本进程的唯一标示符,用来区别其他进程
- 2.内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 3.进程状态:任务状态,退出代码,退出信号等。
- 4.优先级(PR): 相对于其他进程的优先级.(top查看)
NI(nice): 优先级的修正值,PR(优先级)+NI(优先级的修正值) => 最终的PR
通过指令可以调整nice,宏观上往往看不出效果
数字,表示这个进程是先被调度还是后被调度执行数字越小,优先级别越高
- 5.上下文数据:【寄存器】
保存上下文,cpu寄存器的内容保存到内存中
恢复向下文,内存中的寄存器值恢复到cpu中
- 6.记账信息: 每个进程已经在cpu上执行了多久的统计数据
4:进程的状态(保存在PCB)
小辉:进程的状态都有哪些呢?
你知道吗?
- R 就绪状态,进程在就绪队列中(一切准备就绪,等待处理的状态),就会处于这个状态
- 在火车站的窗口前等待买票的队列中排在正在买票的旅客后面的那位旅客的状态
- S 睡眠状态,(单身状态,暂时还轮不到他)
- 在火车票的窗口前等待买票的队列中排在就绪旅客后面的旅客所处状态
- D 深度睡眠状态,密集地进行IO操作的时候(吐coredump)
- T 暂停stop: 可以通过发送 SIGSTOP 信号使进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行
- t 跟踪trace
- X 进程已经结束,只是在Linux 源代码中存在,真实是看不到的
- Z 僵尸进程 和父进程子进程这些有关联
5:文件描述符表
- 文件描述符表:相当于一个数组,数组中存储的是指针。这些 指着分别执行不同的结构体。这些结构体用来存储已经打开的 文件的描述信息
【补充】:
-
文件描述符
-
粗略的讲:一串整形数字(通过这些数字查找描述文件信息结构体的指针)
-
精确的讲:实际上是指向描述文件信息结构体的指针
6:PCB进程控制块与文件描述符表的联系
- PCB进程控制块中存在着一个指向文件描述符表数组 首地址的指针
二:父进程(ppid)与子进程(pid)
2.1:子进程的创建
小辉:小黑,怎么创建子进程啊?
小黑:子进程可以通过父进程中的
fork函数来创建
使用代码来创建子进程
- fork () 系统调用,一个函数
- fork 有两个返回值(1.父进程返回子进程的pid(非负整数>0),2.子进程返回0)
- 父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)
#include<stdio.h>
#include <sys/types.h>
#include<unistd.h>
int main(){
fork();
//gitpid 获取进程的进程
//gitppid 获取父 进程
printf("%d %d\n",getpid(),getppid());
sleep(1);
return 0;
}
为了有更清晰的逻辑,通常利用fork函数的返回值进行分流
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t ret = fork();
if(ret < 0){
perror("fork");
return 1;
}
else if(ret == 0){ //child
printf("I am child : %d!, my father's pid: %d\n", getpid(), getppid());
}else{ //father
printf("I am father : %d!, my father's pid: %d\n", getpid(), getppid());
}
sleep(1);
return 0;
}
2.2:fork ()函数用运(子进程的创建)
1.返回值:一次调用两个返回值
父进程返回子进程的pid,子进程返回 0
通常需要让父子进程执行不同的逻辑,就可以借助fork的返回值来进行区分
- 示例代码
#include<stdio.h>
#include<unistd.h>
int main(){
printf("这一句printf只有父进程执行\n");
pid_t ret=fork();
printf("下面的代码父子进程都执行:\n");
if(ret>0){
printf("father pid:%d,ppid:%d\n",getpid(),getppid());
}else if(ret==0){
printf("son pid:%d,ppid:%d\n",getpid(),getppid());
}else{
perror("fork");
}
sleep(1);
return 0;
}
- 运行结果
- 可以看到子进程的ppid(父进程的pid)与父进程的pid是相同的
【重点】
细心的同学会发现该程序的运行结果中父进程也有父进程,
小黑:小辉我考考你,父进程的
父进程是谁呢?
小辉:这个我知道,父进程的父进程是
shell进程
2.3:循环创建N个进程
- 示例代码
#include<stdio.h>
#include<unistd.h>
int main(){
for(int i=0;i<2;i++){
pid_t ret=fork();
if(ret>0){
printf("%c",'=');
}else if(ret==0){
printf("%c",'=');
}else{
perror("fork");
}
}
printf("\n");
return 0;
}
- 运行结果
- 调用关系
小辉:看看下图中它们的产生关系吧
-
上图可以看你出运行结果不是我们想要的结果(创建n进程)该怎么解决?
-
如下例:
#include<stdio.h>
#include<unistd.h>
int main(){
int i=0;
for(;i<5;i++){
pid_t ret=fork();
if(ret==0){
break;
}elseif(ret==-1){
perror("fork");
}
}
if(i<5){
sleep(i);
printf("child[i]:%d\n",i,getpid());
}else{
sleep(i);
printf("father[i]:%d\n",i,getpid());
}
return 0;
}
- 运行结果
fork 失败了,原因有哪些呢?
如果失败 返回< 0 的结果(可能原因如下)
小黑:如果fork 失败了,原因有哪些呢?
-
a. 内存不够
-
b. 进程太多达到上限 ,上线即为: RLIMIT_NPROC
2.4:执行过程
小辉:他们的运行过程是怎样的呢?
- 1.首先执行父进程
- 2.当遇到fork()函数的时候父进程创建子进程
- 3.若父进程抢到CPU,则父进程继续执行本次执行的剩余代码。若子进程先 “抢到” CPU ,则子进程开始执行自己的逻辑
小黑:睁大眼睛看清楚奥
【注意】:
- 若该过程中有循环的话上一次循环创建的子进程在下一次循环中也会创建新的子进程(自己变成了该子进程的父进程)
2.5:执行的先后顺序
小辉:小黑父子进程的执行有先后顺序吗?比如说
父进程创造了子进程,父进程会先运行吗?
小黑:想啥呢?当然不会啦,它两会抢夺cpu,
谁先抢到谁先执行啊。
- 不确定,取决于操作系统的调度器
- 父子进程执行顺序取决于谁先拿到CPU的使用权
小辉:奥,原来是这样啊,明白了
小辉:那出现上图中的这种原因是啥呢?
小黑:出现这种情况的原因是父进程的父进程
(shell进程)也加入了抢夺CPU的队列,
并且成功的抢到了CPU,shell进程先开
始执行抢到了前台,而第四个子进程占用了终
端.所以才会导致这种情况的出现。
2.6:进程的调度
- 让少量的CPU能够满足大量的进程同时执行的需求
- 并行:两个CPU分别执行两个进程
- 并发:一个CPU"同时"执行多个进程,切换执行,并非同时进行
2.7:父子进程的异同
小辉:小黑,父子进程之间有异同吗?
小黑:当然有啦,虽然它两关系很好,但还是有
区别的
1.父子进程相同处
- 1.全局变量(各自独有),data(各自独有)、text、栈、堆、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式。。。
2.父子进程不同处
- 1.进程ID
- 2.fork返回值
- 3.父进程ID
- 4.进程运行时间
- 5.闹钟(定时器)
- 6.未决信号集
【总结】
- 父子进程间遵循读时共享写时拷贝(另辟空间,针对物理内存)的原则
- data段(0~3G的内容如果父子进程都是都则共享,如果子进程发生写操作则独享)的数据
【重点】
父子进程共享
- 1.文件扫描符(打开文件的结构体)
- 2.mmap建立的映射区
2.8: gdb调试☞跟踪一个进程
1:设置gdb在fork之后跟踪子进程
set follow-fork-mode child
2:设置gdb在fork之后跟踪父进程
set follow-fork-mode parent
【注意】
一定要在fork函数调用之前设置才有效