Linux——进程和线程
1、什么进程?什么是线程?
进程:一个正在运行的程序
线程:进程内部的一条执行路径(序列)
进程是操作系统资源分配基本单位,拥有独立地址空间与系统资源,进程间相互隔离,通信开销大;线程是进程内执行分支,为 CPU 调度基本单位,同进程线程共享资源,仅保有少量私有栈与寄存器,创建切换成本低,也伴随线程同步安全问题。线程依附进程存在,进程可拆分多线程并发工作。
2、为什么需要多线程?
希望有多个处理器,处理器有多个核心,处理起来的速度更快
3、为什么需要进程间通信?
因为进程的独立性,每个进程操作系统都会为该进程创建task_struct(pcb),其中就包含虚拟地址空间描述,页表信息。
因此每个进程内部,数据存储所分配的地址都是虚地址
这也是为什么进程间通信需要操作系统提供进程间通信资源的原因,让操作系统给多个不同的进程分一块大家都能访问的空间
4、内存置换
当内存不够用了,操作系统如何处理?
内存置换
操作系统认为内存中的很多数据,并不是一直在访问(热数据)
因此当内存不够用的时候,操作系统就会根据一定的算法将指定内存中的数据置换出去(存的硬盘),存放这些数据的硬盘区有个专有名词:交换分区
LRU:最久未使用; LFU:最少未使用;FIFO:先进先出
5、缺页中断
当访问内存数据的时候,通过页表将虚拟地址转换为物理地址,但是转换的时候,发现页表中置为了缺页中断位(当前这个虚拟地址对应的那块内存数据,没有在物理内存中)
缺页中断的处理:从交换分区将数据重新置换到内存中,更新页表信息
分段式内存管理:将代码分成一个一个的代码段....对程序内的内存管理比较友好,但是数据会堆在一定区域,造成内存浪费
段页式内存管理:对内存分段,在每个段内进行分页
6、进程控制:创建,退出,等待,程序替换
1.进程创建:pid_t fork();
功能:通过复制父进程的方式,创建一个子进程
复制了什么:缓冲区;调度切换相关的上下文信息;虚拟地址空间+页表信息;文件描述符信息
创建子进程这里使用了写时拷贝技术:创建子进程之后,子进程与父进程指向同一块内存区,但是当任意一方要对一块内存区域的数据进程修改则给当前进程重新开辟一块空间拷贝新数据进去,大大提高了创建子进程的效率
pid_t pid=fork(); if(pid==0) { //子进程运行 } else { //父进程运行 }1)复制了虚拟地址空间
复制出来的子进程,要执行的代码,要处理的数据跟父进程是一样的
2)复制了程序调度上下文信息(为什么fork出来的子进程是从fork之后开始运行)
pc寄存器:保存的是即将要执行的指令地址(程序运行到了哪里),切换的其他信息:正在执行的指令,正在处理的数据...
2.进程的退出
退出:终止程序的运行
正常退出:
1)在main中return;
2)在任意位置调用exit()函数——是一个库函数(封装了系统调用接口)
3)在任意位置调用_exit()函数——系统调用
库函数是对系统调用函数在特定场景下的一些功能补充,库函数内部调用了系统调用函数
库函数在exit退出的时候,会进行用户态空间做一些资源清理,eg.刷新缓冲区
异常退出:
程序在运行遇到了某些异常,导致程序崩溃,异常退出情况下,进程的返回值是没有参考意义的
3.进程等待
父进程创建子进程之后,等待子进程退出
为什么要等待?
捕获子进程的退出状态,获取他的返回值,释放子进程的所有资源(避免产生僵尸进程)
pid_t wait(int* wstatus);
pid_t waitpid(pid_t pid,int*wstatus,int options);
wstatus:内存包含两个信息:1.进程退出原因;2.进程退出码
处理逻辑:先取第七位,判断是否为0,为0则表示正常退出,再取低16位中的高8位,当作退出码进行处理
4.进程程序替换
默认创建子进程之后,子进程与父进程的代码段一样的,可以通过fork返回值进行分支控制,这样会导致两个不同的功能,放在一个程序中实现,代码较为臃肿
如何让子进程重新运行另一个程序呢?使用程序替换
1.将新程序(指令+数据)加载到内存中
2.将子进程的页表映射信息初始化,并且更新映射到新的内存区域(加载到内存的新程序)
3.初始化调度上下文信息(让当前进程pc寄存器指向新程序指令的起始位置)
exec函数族:execl,execlp,execle,execv,execvp,execve;
1)l和v的差别:设置程序运行参数的差别(I是通过不定参逐个给与;v是组织成为数据一次给与)
2)有没有p的差别:是否会到系统path环境变量指定的路径下去找这个程序
execl(/bin/ls) execlp(ls)
3)有没有e的差别:子进程中是否自定义环境变量
7、进程中打开的文件信息管理
进程的i/O:文件的输入输出
系统调用:open,read,write,lseek,close
文件描述符的本质:是一个数组下标
1)一个进程内部能够打开的文件数量是有上限的
2)重定向就是修改描述符所对应的描述信息而实现的
重定向:修改数据的输出位置
每个进程运行起来之后,会默认打开三个文件:标准输入-键盘,标准输出-显示器,错误输出-显示器
描述符:0
8、进程间通信
进程间通信就是让操作系统为进程之间提供一个公共内存访问区。
原因:进程之间具有独立性
根据不同的应用场景操作系统提供了多种不同的方式:管道(数据传输),共享内存(数据共享),消息队列(数据传输),信号量(同步控制)
(追问:信号,网络通信)
1.管道
管道的本质:内核中的一块缓冲区
公共特性:
1)半双工通信:可以选择方向的单向通信
2)管道的数据写满了,继续write就会阻塞;管道中没有数据,read会阻塞
3)管道读端关闭,则write就会触发异常-SIGPIPE;所有写端关闭,read取完所有数据后,继续read不在阻塞,而且返回0
4)数据传输是字节流传输:以字节为单位进行传递,并且有序交付(先进先出)
5)生命周期:随进程,所有打开管道的进程退出,内核中的管道这个缓冲区就会被释放
匿名管道:没有名字的管道,没有标识符,其他的进程怎么找到这个管道跟我通信呢?
特性:只能用于具有亲缘进程关系的进程间通信(创建子进程,复制父进程的文件信息表)
操作:在创建子进程之前创建管道,然后创建子进程(子进程就是复制进程内部打开的文件信息)
int pipe(int fd[2])接收两个描述符
fd[0]用于读,fd[1] 用于写
命名管道:有名字的管道,可以被所以进程找到,可以用于任意进程间通信
名字:一个管道文件(这个只是一个标识名字,本质上管道就是内核的缓冲区)
接口:mkfifo(filename,flag);
2.共享内存
本质:内核中创建的一块内存空间
共享原理:将这块内存空间,映射到不同的进程的虚拟地址空间
操作:
shmget(key_t key,size,flag)以及一个标识key创建共享内存
void* shmat(int id);将创建的共享内存,映射到自己的地址空间中,返回映射后的地址
通过shmat返回的地址,对共享内存的数据进行操作
void shmdt(int id);解除映射
int shmctl(int id,oprator -IPC_RMID) 删除共享内存
我删除共享内存的时候,其他的进程正在使用呢?
一旦进行了删除,内核中移除了共享内存的名字(禁止其他进程继续映射)
但是不会直接删除这块共享内存,而是共享内存的描述信息中有个计数器(当前映射连接数)
因此当当前映射连接数为0时才会自动释放
ipcs查看共享内存
