一、进程基础
操作系统基础的执行单元,调度单位
静态数据:只占用磁盘空间,不消耗其他资源
动态数据:磁盘 内存 CPU
1. 编译器将源码编译成一个可执行文件.exe/.elf
2. 运行后系统生成一个同名的进程
程序是进程的静态表现,进程是程序的动态表现
3. 进程在创建时,要分配虚拟内存(线程不分配内存)
进程是最小的分配资源单位,也是调度单位
因为每个进程创建时分配内存资源,所以被成为分配资源单位
进程没有实体,由复杂的逻辑关系构成
进程的生存环境,形态:
PCB 进程控制块,是一个300多个成员的结构体,记录了详细的进程信息
虚拟内存地址通过内存映射访问真正的物理内存地址
进程中虚拟地址不同,但是指向相同的物理地址,实现共享内存【内核空间】
不同的进程内存独立【用户空间】(以防互相影响,内核空间只有系统拥有权限,不怕被修改)
程序优化:系统占用以及系统开销(CPU 内存)
内存基本单位:Page(页) 1Page=4KB=4096Bytes
malloc(8192) ——> 一次分配两页内存
malloc(5000) ——>分配两页内存,对剩余内存空间进行限制访问
malloc(3192) ——>检查是否使用完毕,并解除限制
内存的权限:PROT_READ 只读 PROT_WRITE 只写 PROT_EXEC 执行 PROT_NONE 无权限
虚拟内存:32位系统,三级间接寻页:1024*1024*1024=GB
63位系统,四级间接寻页,1024*1024*1024*1024=TB
二、进程状态以及状态转换
进程的五种常态
就绪态Ready:进程已准备除处理器外所需资源,等待cpu资源
运行态Running:占用cpu,正在执行指令
阻塞态Blocked:由于进程等待某种条件(如I/O操作或进程同步)被暂停(释放cpu),在条件满足之前无法继续执行。
挂起态Suspend:由于用户和系统的需要被暂停(释放cpu),把进程从内存转到外存
终止态Terminated:进程退出,释放进程内存资源
阻塞态和挂起态的区别?
1. 阻塞态可以被中断,在等待的资源得到满足后,进入就绪状态;挂起态无法被中断,只有将其挂起的对象唤醒后,才可以让其继续
2. 阻塞态释放了cpu,但是阻塞的进程还在内存中;挂起的进程通过swp交换到外存中(磁盘空间)
3. 阻塞态发生在进程等待资源时,挂起是由于用户和系统需要
任意状态都可以切换为终止态
Linux独有的状态
僵尸态Zombie:子进程退出结束,但是PCB残留,导致内存泄漏,叫做僵尸进程
孤儿态Orphan:父进程先挂掉,子进程会被1号进程领养,我们可以理解为操作系统,或者说父进程先结束,子进程必须被操作系统领养,因为无主进程,一直僵尸浪费资源,我们将这种进程称之为孤儿进程。 还会将自己变成后台进程。
分时复用原则(时间片)
多进程共享使用CPU,每个进程按时间片(10us)使用,而后快速切换,既可以让多个进程共享使用,以提高cpu使用率
单任务操作系统(软盘) ——> 多任务操作系统(充分共享使用硬件)
大多数系统的程序都是并发执行的(快速交替执行)
保存与恢复处理器现场
CPU原件:控制器、寄存器(Eax,Ebx,Edx,Eflag....)、编码器、译码器、运算器
寄存器:
内存:快速缓存数据及计算过程
每个进程都有自己独立的PCB,每个PCB都有一个内核栈指针kernel_ptr,指向自己的内核栈
多任务基准:分时+保存与恢复
什么是线程?什么是进程?
进程或者线程就是寄存器(CPU的访问权)和栈(内核栈)
进程、线程、纤程:调度单位
协程、管程:管理单元
超线程:硬件,模拟多核
二、进程源语
2.1 进程源语函数
系统支持进程开发提供的一系列API函数接口
pid_t pid;进程id类型
pid_t pid=getpid(void);//调用获取进程pid
2.1.1 fork() 创建子进程
pid_t pid=fork(); //创建子进程,执行一次创建一个进程
子进程与父进程pid连续,父进程最小
父进程调用fork,系统创建子进程,子进程从fork之后执行
关于子进程继承的问题?
1. 拷贝继承
2. 区分父子进程代码段,实现父子执行不同的代码任务
使用fork的返回值pid进行父子进程的区分
子进程不允许踏出工作区
1. 永久阻塞 2. 进程退出
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>//parent start
int main(){pid_t pid;pid=fork(); //创建子进程if(pid>0){printf("parent pid %d, Running...\n",getpid());}else if(pid==0){//child startprintf("child pid %d, Running...\n",getpid());while(1)sleep(1);//child end}else if(pid==-1){perror("fork call failed");exit(0);}printf("self id %d\n",getpid());while(1)sleep(1);return 0;
}
//parent end
windows的printf自带刷新缓冲区
linux下\n刷新缓冲区
3. 创建多进程
循环创建多进程
int i;
for(int i=0;i<3;i++){pid=fork();if(pid==0)break;
}
4. 子进程执行fork,并且得到返回值0
两个进程执行同一个fork()
栈帧
不同的函数有各自的函数栈帧地址,其他位置无法访问函数内部资源
父子进程可以共享同一个函数的栈帧地址,实现调用同一个函数,执行一部分
gdb可以实现栈帧的跳转
第一版 fork,拷贝继承版本
如果子进程不需要拷贝父进程数据,而是自行获取用户空间,那么父进程的拷贝流程和开销变成无意义的系统开销
第二版 vfork,无拷贝过程,但是不能直接使用
通过vfork创建的子进程没有用户空间,需要用户自行生成用户层,vfork必须结合execl函数使用
第三版 fork,读共享写复制
读时共享,虚拟地址不同,物理地址相同
父子进程写都会导致子进程复制共享内存数据
例题:
2*9+1=19个子进程
2*10-1=19
2.1.2 execl() 进程重载
系统名都是程序,执行命令时创建进程,完成特定功能
重载只替换进程工作数据,与PCB进程信息无关
execl("程序路径",argv[0]/*文件名|命令*/,argv[1]/*参数*/,NULL/*哨兵,结束*/);
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>int main(){pid_t pid;pid=fork();if(pid>0){printf("parent pid %d\n",getpid());while(1)sleep(1);}else if(pid==0){printf("child pid %d\n",getpid());printf("child execl...\n");execl("/usr/bin/firefox","firefox","www.bilibili.com",NULL);printf("execl failed\n");exit(0);}else if(pid<0){printf("fork failed\n");exit(0);}return 0;
}
使用execl
1. vfork创建子进程无法直接使用,必须重载才可以使用
2. 重载方便程序的功能扩展,很多现有的命令在进程中可以直接使用
3. 动态迭代
在程序不下线的情况下,动态更新功能
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>int main(){pid_t pid;printf("MyWeb Vesion 1.o Running...\n");while(1){pid=fork();if(pid==0)break;sleep(5);}if(pid==0)execl("./MOD/mod1","mod1",NULL);return 0;
}
此时只需要修改mod1.c文件内容并重新编译,就可以在线修改主程序的功能
2.1.3 wait() waitpid() 回收函数
僵尸态Zombie:子进程退出结束,但是PCB残留,导致内存泄漏,叫做僵尸进程
_EXIT(0) 结束进程时,完全释放了用户空间,释放了部分内核空间,只有PCB残留——内存泄漏
危害:1. 内存泄漏 2. 僵尸进程残留PCB无法给新进程使用,影响进程创建数量
系统为何要保留PCB?
僵尸进程需要父进程进行手动回收、检验子进程是否正常退出
关于ps -aux进程状态stat的中Ss、S<l、Ssl、SLl、SNl、R、R+的解释_ps aux 进程 tl sl-CSDN博客
int stat;
pid_t zpid=wait(NULL); //表示只回收pcb不进行回收的验证操作,返回值为僵尸进程pid
pid_t zpid=wait(&stat); //返回子进程状态
wait() 失败原因:没有子进程立即失败
wait() 是阻塞函数,阻塞回收,如果子进程未结束,无需回收,wait阻塞等待
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>int main(){pid_t pid;pid_t zpid;pid=fork();if(pid>0){zpid=wait(NULL);printf("parent pid %d, child pid %d, zpid %d\n",getpid(),pid,zpid); while(1)sleep(1);}else if(pid==0){printf("child alive 10s\n");sleep(10);exit(0);}else {perror("fork failed");exit(0);}return 0;
}
只有父进程可以回收子进程,其他进程无法代替
init 1号进程没有父进程,称作根进程
子进程先于父进程死亡,必然会成为僵尸;父进程先于子进程死亡,子进程必然会成为孤儿
waitpid(pid_t pid,int* state,WNOHANG/*非阻塞关键字*/);
//返回值:>0 成功返回| -1 失败| =0 非阻塞返回pid==-1 回收任意子进程pid>0 回收指定子进程(进程号)pid==0 同组回收pid<-1 跨组回收(-进程组号)
waipid() 支持非阻塞回收,相比wait() 更加灵活。在等待回收时,父进程可以执行自己的任务和代码。
Linux之进程的基本概念(进程,进程组,会话关系)_linux 会话id-CSDN博客
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>int main(){pid_t pid;pid_t zpid;pid=fork();if(pid>0){while((zpid=waitpid(-1,NULL,WNOHANG))!=-1){if(zpid>0)printf("parent pid %d, child pid %d, zpid %d\n",getpid(),pid,zpid); else if(zpid==0){printf("parent exec jobs\n");sleep(1);}}while(1)sleep(1);}else if(pid==0){printf("child alive 10s\n");sleep(10);exit(0);}else {perror("fork failed");exit(0);}return 0;
}
2.2 多进程拷贝
单进程的CPU使用资源受限,可以采用多进程模型,获取更多的时间片,加快任务完成速度,提高效率
两种常见的多进程:多进程任务较为复杂;多进程任务单一
串行:逐次完成任务
并行:多核CPU且每一个工作进程都能加载到对应的处理核心上【由CPU决定,不可控】
并发:单个处理器,逻辑上同步执行【有并行的概率】多进程并发、多线程并发
程序的执行效率由cpu的占用及使用频率决定,占用越多效率越高
单进程模型因阻塞或其他原因放弃本轮cpu使用,但是多进程或者多进程模型,根据就近原则,放弃的进程可以交替给相邻的进程,让任务继续