Linux编程1:进程和线程
进程和线程
1.进程
1.进程基本概念
- 进程:是程序动态执行的过程,包括创建、调度、消亡。
- 程序:存放在外存中的一段数据的集合
2.进程创建
1进程空间分布
- 每个进程运行起来后,操作系统开辟0-4G虚拟内存空间
- 空间:用户空间+内核空间(无法访问)
- 进程用户空间:文本段(文本区)+数据段(数据区)+系统数据段(堆区、栈区)
- 文本段:存放代码和指令
- 数据段:字符串常量、已初始化全局变量/静态变量,未初始化全局变量/静态变量
- 系统数据段:堆区(malloc空间)、栈区(局部变量、函数运行)
- 数据区存放数据特点:
- 未经初始化为0值
- 编译时开辟空间
- 程序结束时回收空间
- 堆区存放数据特点:
- malloc申请堆区空间
- free释放堆区空间
- 注意:避免未释放空间导致内存泄漏
- 栈区空间存放数据特点:
- 未经初始化为随机值
- 执行到变量定义开辟内存空间
- 超过变量作用域回收变量空间
2虚拟地址和物理地址
1.虚拟地址:
所有能够被用户看到的地址均为虚拟地址,表示用户可以寻址的范围2.物理地址:
内存存放数据对应的实际硬件物理地址3. MMU
实际地址和物理地址之间的映射由内存映射单元(MMU)完成
3多进程存储:
1存储方式:
- 多个进程空间在操作系统中存储时,空间(物理地址是独立的)
- 多个进程在操作系统中共用一个虚拟内存空间(虚拟地址是共享的)
4多进程的调度:
1常见的进程调度算法:
- 1先来先执行,后来后执行
- 2高优先级调度算法
- 3时间片轮转调度算法(时间片:在某一段进程中运行的一段时间)
- 进程1中时间片运行到达了,进程1就向CPU发送中断信号,CPU进行在虚拟地址空间进行保存现场操作,进程1进入内核中的一个进程管理块区域,通过MMU内存映射单元把进程1的数据存入进程1的物理地址空间,CPU执行进程2...再次执行进程1时,从进程1的物理地址进行拿到进程1 的相关数据,在虚拟地址空间进行恢复现场操作。
2多进程调度本质:
- 宏观并行、微观串行
- 宏观分析:一个CPU同时执行多个进程任务
- 微观分析:一个CPU在多个任务中高速切换保障多任务执行
5进程相关的命令
1. top
示例:top
功能:根据CPU占用率和内存占用率查看当前所有进程的信息
PID:唯一区分进程的ID号
USER:创建者
PR NI:优先级
VIRT
RES
SHR:资源占用
S:状态
%CPU:CPU占用率
%MEM:内存占用率
TIME+:运行的时长
COMMAND:进程命令q退出
2. ps -ef
示例:ps -ef
功能:查看该时刻的所有进程信息
UID:创建进程的用户
PID:进程的ID号
PPID:父进程的ID号
C
STIME
TTY:进程依赖的终端
TIME
CMDps -ef | grep 进程名
查找与进程名对应的进程信息
3. ps -aux
示例:ps -aux
功能:查看该时刻的所有进程信息
USERPID
%CPU
%MEM
VSZ
RSS
TTY
STAT:进程的状态
START TIME
COMMAND
4. 后台执行进程任务
示例:./a.out &
功能:后台执行a.out进程
5. jobs
示例:jobs
功能:查看终端下所有的后台进程任务
6.fg
示例:fg 编号(通过jobs查看)
功能:后台执行的任务放到前台执行
7. nice/renice
示例:nice -n 优先级编号 进程名renice -n 优先级编号 进程PID
功能:改变进程的优先级
优先级范围:-20 - 20
数字越小优先级越高
8kill/killall
示例:kill -编号 进程PIDkillall -编号 进程名
功能:杀死进程任务
kill -9 PID
6进程的状态
进程状态 | 标识 | 含义 |
就绪态/运行态 | R | 进程位于CPU任务调度的队列中 |
可唤醒等待态 | S | 等待某个资源的到来,资源到来后被唤醒加入调度队列br等待过程中可以被打断 |
不可唤醒等待态 | D | 等待某个资源的到来,资源到来后被唤醒加入调度队列br/>等待过程中不可以被打断 |
停止态 | T | 用户人为让进程任务暂停等待 |
僵尸态 | Z | 进程代码运行结束,空间尚未回收 |
结束态 | X | 进程代码运行结束,空间被回收 |
3.进程相关函数接口
1. fork
原型:pid_t fork(void);
功能:创建一个新的进程,新的进程称为子进程,调用fork的进程称为父进程
参数:缺省
返回值:父进程中返回子进程的PID子进程中返回0 出错返回-1
注意:
- 子进程拷贝父进程文本段、数据段、系统数据段
- 父进程与子进程空间独立,同一份代码中的变量和数据都会在父子进程中各有一份,父子进程 修改自己空间的数据不会影响对方的空间
- 进程的PID不一样
- fork的返回值不一样,父进程中返回子进程的PID,子进程中返回0
- PID:一定是 > 0
2. getpid和getppid
原型:pid_t getpid(void);
功能:获得调用函数进程的PID号
原型:pid_t getppid(void);
功能:获得调用函数进程的父进程的PID号
tip::子进程不影响父进程且子进程会copy父进程的缓存区int main(void) {pid_t PID1 = 0;printf("Hello World\n");PID1 = fork();if (-1 == PID1){perror("fail to fork");return 01;}if (0 == PID1){printf("hello\n");}else if (PID1 > 0){printf("world\n");}while (1){}return 0; } // 注意:若第6行打印Hello World没有\n --------输出结果为: // Hello Worldworld // Hello Worldhello// 若第6行打印Hello World有\n --------输出结果为: // Hello World // word // hello// 为什么呢 因为正常情况是执行过fork之后 父子进程才会分别执行(前面的不会重复执行); // 但是由于printf是库函数,标准IO具有缓存区(遇到\n刷新), // 当没有\n时刻,没有刷新缓存 fork复制父进程的数据时 缓存区一起复制到子进程了,因此会一起打印// 对于一个变量 子进程对它改变了值 不会影响父进程里这个值 // (子进程是父进程copy去的)他们存储在不同的数据空间 后续子进程的变换和父进程无关
3. exit与_exit
原型:void exit(int status);
功能:结束进程任务并返回进程结束状态
参数:status:进程结束状态的值原型:void _exit(int status);
功能:结束进程任务并返回进程结束状态
参数:
返回值:
注意:
- 在主函数中调用exit和return功能保持一致
- return在函数内部将结束该函数
- exit在函数内部会将进程结束
- exit会在结束前刷新缓存区
- _exit不会刷新缓存区
4进程回收
- wait
原型:pid_t wait(int *wstatus);功能:回收子进程空间
参数:wstatus:存放子进程结束状态空间的首地址返回值:成功返回回收到的子进程的PID失败返回-1
注意:
wait具有阻塞等待功能,等到有子进程结束才会回收子进程继续向下执行
- waitpid
4进程消亡
1孤儿进程:
父进程先结束,子进程会成为孤儿进程,被int进程收养
2僵尸进程:
- 是每个进程结束必然会经历的阶段
- 产生原因:子进程结束之后,父进程没有回收子进程空间,导致进程执行结束,空间依然被占用的状态,称为僵尸进程
- 如何避免产生僵尸进程?
- 让父进程先结束,子进程会成为孤儿进程,孤儿进程被init进程收养,子进程再结束,init进程回收进程空间
- 子进程结束,父进程回收子进程,避免产生僵尸进程
ex:编写一个程序,将当前系统中所有进程的PID、PPID、Name信息记录到record.txt文件中
(/proc目录下,每个纯数字组成的文件夹就是一个进程,也是该进程的PID号)