前言
为什么学习多进程线程:
在我们学习该课程之前,我们代码的执行逻辑,从上往下,一条一条语句执行: 先干什么,后干什么。学习多线程进程,可以实现多任务同时运行,互相切换互不影响,拜托传统代码逻辑执行的弊端。任务会频繁切换给用户多个任务一同运行的感觉。
什么是任务:
简单讲:一个任务就是一个程序的一次运行,一个任务包含一个或者多个独立功能的子任务,这个独立的子任务是进程或者线程。
一、进程基础
1.1 进程的概念
1.1.1 什么是进程
进程是程序执行和资源管理的最小单位,是一个动态的概念,它是程序执行的过程,包括创 建、调度和消亡。
1.1.2 什么是程序
程序:静态指令的集合,程序就相当于你的计划书。
1.1.3 进程与程序的区别
程序是静态的,它是一些保存在磁盘上的指令的有序集合,没有任何执行的概念, 进程是一个动态的概念,它是程序执行的过程,包括创建、调度和消亡一个程序可以对应多个进程,一个进程只能对应一个程序。
1.1.4 进程包含什么
(1)“数据段”存放的是全局变量、常数以及动态数据分配 的数据空间(如malloc函数取得的空间)等。
(2)“正文段”存放的是程序中的代码。
(3)“堆栈段”存放的是函数的返回地址、函数的参数以及 程序中的局部变量 。
1.1.5进程的特点:
1.2进程的管理手段:
windows: 管理手段:任务管理器 进程处理者:CPU
linux: 管理手段:PCB >>Process Control Block (进程管理块)
是linux系统用来管理进程的手段,由内核创建并维护的 是一个非常巨型结构体: struct task_struct
进程处理者:CPU
1.2.1任务状态变迁
1.2.2 进程的分类:
(1)交互式进程 : 由shell终端产生并控制的 如:./a.out
(2)批处理进程 : shell脚本:指令一批一批执行
(3)守护进程 : 周期性的在后台等待或执行某一个任务 如: ssh samba tftp nfs 木马
1.2.3进程的查看:
查看状态:
ps -aux :静态查看进程
top :动态查看进程(每三秒刷新一次)
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
USER:进程拥有者
PID :进程的ID号,是进程的唯一标识,不重复 :越早开启的进程,PID越小
%CPU: CPU的占用率
%MEM:物理内存占用率
VSZ :虚拟内存用量
RSS :物理内存用量
TTY :终端的使用,如果该进程没有终端:显示?
STAT:进程的状态
进程状态:
S:休眠(可以中断) D:休眠(不可中断) I:空闲 Idie T:暂停 X:死亡 Z:僵尸状态 R:正在运行 s:进程的领导者(多进程) <:优先级高 N:优先级低 l:线程的领导者(多线程) +:前端进程
休眠(可中断):进程处于阻塞状态,正在等待某些事件发生或能够占用某些资源。处于这种状态下的进程可以被信号中断
僵死状态:子进程运行结束,父进程未退出,并且未使用wait函数等系统调用来回收子进程的退出状态。
START:进程的开始执行时间
TIME :运行时间
COMMAND:产生进程的命令 :./a.out
优先级:
linux系统有140个优先级:数字越小,优先级越高
实时进程: 0 - 99 100个
非实时: 100- 139 40个
通过:ps -le查看优先级
PRI NI
PRI:优先级
NI :谦让值(-20 ~ 19 ) 数字越小,越不谦让
1.3进程的调度
进程互斥:
进程互斥是指当有若干进程都要使用某一共享资源时,任何时候最多允许一个进程使用,其他要 使用该资源的进程必须等待,直到只用自愿者释放了该资源。
进程同步:
一组并发进程按一定的顺序执行的过程称为进程间的同步。具有同步关系的一组并发进程称为合作进程,合作进程间互相发送的信号称为消息或事件。
临界资源:
操作系统中将以此只允许一个进程访问的资源称为临界资源。
临界区:
进程中访问临界资源的那段程序代码称为临界区。为实现对临界资源的互斥访问,应保证诸进程互斥地进入各自的临界区。
进程的调度:
抢占式和非抢占式调度
二、进程的API学习:
2.1 进程的创建 :
生(fork)
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
功能:
创建子进程
参数:
void:空
返回值:
创建成功:
父进程返回子进程的PID号
子进程返回0
失败:
返回-1,并设置错误码
2.1.1fork()特点:
1>创建出来的进程是以什么样的方式存在?
在fork()执行后的下面所有的代码,都会被父子进程共同执行
2>同一程序,多次fork,得出来的进程个数,是有规律的,参考图片
3>fork以下的所有语句,都会被父子进程共同执行 如何限定操作:将父子进程的操作区分开:
父子进程:返回值有区别,所以我们要从两者的返回值入手
父进程的返回值:子进程的PID号 PID号必会大于0
子进程返回值:0
if(pid>0){
//父进程的操作
}else if(pid==0){
//子进程的操作
}else{
//失败操作
}
2.1.2获取pid
1>如何在进程中获取PID号?
1>获得本身的PID号
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
功能:
获得执行该函数的进程的PID号
返回值:
成功返回该进程的PID号
失败返回-1,并设置错误码
2>如何在进程中获取其父进程的PID号
#include <sys/types.h>
#include <unistd.h>
pid_t getppid(void);
功能:
获得执行该函数的进程的父进程的PID号
返回值:
成功返回该父进程的PID号
失败返回-1,并设置错误码
2.1.3父子进程关系
父子进程之间的关系:
两个进程无不干扰:
1>杀死父进程,保留子进程:功能正常运行
2>保留父进程,杀死子进程:子进程会变成僵尸状态
原因:子进程所占用系统资源并没有被释放 死了,但没完全死
解决:杀死父进程,让系统介入回收资源,父进程执行收尸操作,回收子进程资源
父进程的父进程为终端。
执行终端:控制终端
2.2 进程的操作替换
何时使用:
(1)当进程认为自己不能再为系统和用户做出任何 贡献了时就可以调用exec函数,让自己执行新 的程序
(2)如果某个进程想同时执行另一个程序,它就可 以调用fork函数创建子进程,然后在子进程中 调用任何一个exec函数。这样看起来就好像通 过执行应用程序而产生了一个新进程一样
2.2.1exec函数族
exec函数族
#include <unistd.h>
extern char **environ;//外部引入
int execl(const char *path, const char *arg, ...
/* (char *) NULL */);
int execlp(const char *file, const char *arg, ...
/* (char *) NULL */);
int execle(const char *path, const char *arg, ...
/*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
char *const envp[]);
分析:
1>exec开头的:exec函数族
2>带p的:表示根据文件名寻找要替换的程序 ,默认回去/bin 和/usr/bin 去找不带p的:表示根据路径寻找要替换的程序
3>带l的:表示以列举的方式寻找要替换的程序
4>带v的:表示以数组的方式寻找要替换的程序
5>带e的:表示以环境变量的方式传递数据,并且替换程序
返回值:
只有发生错误时才会有返回值
失败返回-1,并设置错误码
测试:
以最后以一个execvpe函数为例:
char *envp[]={"hello"," world",NULL}; //数组最后以NULL结尾,表示读取到最后条件
execvpe函数则是讲程序跳转到new可执行程序中。讲数组的内容写入到grgv中,最后结束程序。
2.2.2 补充:Linux环境变量的修改:
1>临时修改:只对当前终端有效:一次性
export PATH=$PATH:/home/pm/shared/pro/day2_code
2>永久修改:只以用户为单位的情况下修改:针对普通用户
1>打开用户的环境变量文件
sudo vim ~/.bashrc
2>在里面添加一句话
export PATH=$PATH:/home/pm/shared/pro/day2_code
3>保存并退出
source ~/.bashrc
3>永久修改:所有用户都可以用 :针对所有
1>执行命令:
sudo vim /etc/environment
2>在文件中添加
:/home/pm/shared/pro/day2_code
3>保存并退出
2.3进程阻塞
1>病:进程的阻塞:
进程互斥
进程互斥是指当有若干进程都要使用某一共享资源时,任何时候最多允许一个进程使用,其他要 使用该资源的进程必须等待,直到只用自愿者释放了该资源
进程同步
一组并发进程按一定的顺序执行的过程称为进程间的同步。具有同步关系的一组并发进程称为合作进程,合作进程间互相发送的信号称为消息或事件。
临界资源
操作系统中将一次只允许一个进程访问的资源称为临界资源
临界区
进程中访问临界资源的那段程序代码称为临界区。为实现对临界资源的互斥访问,应保证诸进程互斥地进入各自的临界区。
2.3.1 进程的阻塞
flock --->给文件描述符指代的临界资源上锁
#include <sys/file.h>
int flock(int fd, int operation);
功能:
给fd(文件描述符) 做 上锁操作
参数:
fd:文件描述符
operation:锁的操作
LOCK_SH:共享锁(上锁) //可以双方同时操作
LOCK_EX:互斥锁(上锁)
LOCK_UN:解锁
LOCK_NB:不上锁
返回值:
成功返回0
失败返回-1,并设置错误码
flock_1.c:
flock_2.c:
结果:
1.txt文件内容:
2.4进程的退出
进程有哪几种死法:
1>老死 -->程序运行完毕
2>被杀死 -->接收到 kill -9 信号
3>自杀 -->提前遇到了return 和 exit _exit
自杀: 1>exit 2>_exit 3>return
1>exit --->结束进程
#include <stdlib.h>
void exit(int status);
功能:
结束进程
参数:
status:进程退出时的状态:进程的遗言
2>_exit --->结束进程
#include <unistd.h>
void _exit(int status);
功能:
结束进程
参数:
status:进程退出时的状态:进程的遗言
总结:
exit在结束进程时,会刷新缓冲区
_exit直接结束进程,不会做其他任何操作
3>return
return用来结束函数的
但是可以通过结束main函数来达到结束进程的目的
2.4.1 测试:
遇到exit函数正常退出,则不执行之后的程序,且会刷新缓冲区
遇到_exit函数异常退出,则不执行之后的程序,不会刷新缓冲区。
2.5进程的收尸
进程的收尸:
收尸:释放进程运行时向系统申请的资源
概念:只能父进程给子进程收尸
1>wait
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
功能:
收尸操作:带阻塞
父进程阻塞等待子进程死亡,子进程死亡后做收尸操作
参数:
wstatus:接收收尸子进程的遗言
返回值:
成功返回 被收尸子进程PID号
失败返回-1,并设置错误码
PS:
wait有两套收尸逻辑:
1>谁先死,收谁尸 --->优先
2>谁的死亡时间越接近父进程执行收尸的操作时间,收
谁的尸
2>waitpid-------!!!!!!!!!!!
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);
功能:
给子进程收尸 --->指定收尸
参数:
PID:进程号:取值范围 -----!!
>0: 给特定的子进程收尸(进程号为PID)
例子:waitpid(12345,..,..)
=0:给当前进程在同一进程组内的子进程收尸
=-1:给任意子进程收尸,无论他在哪
<-1:给 进程组ID号为 |pid| 下的子进程收尸
-12345 ---> 12345 然后收掉12345下子进程的尸
wstatus:子进程的死亡状态
options:
0:代表阻塞等待收尸
WNOHANG:不阻塞直接收尸
WUNTRACED:分析子进程的死因--->验尸 -----!!
WIFEXITED:如果子进程正常死亡,则为true
采用WEXITSTATUS 去获取死因
WIFSIGNALED:如果子进程是被信号杀死,则为true
采用WTERMSIG 去获取死因
WIFSTOPPED:如果子进程是被信号暂停,则为true
采用WSTOPSIG 去获取死因
返回值:
如果阻塞状态下成功返回 被收尸的子进程PID号
如果不阻塞状态下:
成功返回 被收尸的子进程PID号
失败返回 0 -->没收到尸
失败返回-1,并设置错误码
wait函数:
测试:
程序流程图: