3 多进程
理论基础
引入目的
- 实现多任务并发执行
- 数据在多个进程之间通信
基本概念
- 进程是程序的一次执行过程,有一定的生命周期:创建态,就绪态,执行态,挂起态,死亡态
- 系统中允许的每个进程都是拷贝父进程资源得到的
- 进程退出后,分配的系统资源应该由父进程回收,否则会造成浪费资源
- 进程是计算机资源分配的基本单位,系统会给每个进程分配4G的虚拟内存
- 进程调度机制:时间片轮询上下文切换机制
- 并行和并发区别:
- 并发:单核CPU系统在处理多个任务时,使用相关的调度机制实现多个任务进行细化时间片轮询,在宏观上感觉是多个任务同时执行的操作,但同一时刻只有一个任务在被CPU处理
- 并行:多核CPU处理多个任务时,同一时间,每个CPU处理的任务之间是并行的,真正意义上多个任务同时执行
进程的内存管理
- 启动一个进程时,操作系统通过mmu(内存管理单元)将物理内存映射到虚拟内存
- 虚拟内存:
- 3-4G:内核空间,多个进程共享一个内核空间
- 0-3G:用户空间,不同进程的用户空间相互独立
- 动态区:程序运行时自动产生,该区未初始化的变量默认随机值
- 栈区:局部变量,可读可写,从函数进入到函数return,由编译器管理
- 堆区:malloc和free,可读可写,由程序员管理
- 静态区:程序生成后就有
- 数据区:全局变量和静态变量(static),可读可写,从程序执行到程序退出,常驻内存,该区未初始化的变量默认是0
- rodata(read only data)区:常量,只读数,从程序执行到程序退出,常驻内存
- 代码区:函数实现,只读,从程序执行到程序退出,常驻内存
- 动态区:程序运行时自动产生,该区未初始化的变量默认随机值
- 虚拟内存:
- swap分区:硬盘上的一个分区,内存空间不够用时,会将长时间不用的进程先放入交换分区,物理内存够用时再切换回去
进程的种类
- 交互进程:由Shell控制,可以直接和用户交互,如文本编辑器
- 批处理进程:内部维护一个队列,队列中的进程会被统一处理,如g++一步到位的编译
- 守护进程:脱离了终端,随系统启动而运行,随系统退出而停止,如系统的服务进程
特殊进程
- 0进程(idel):空闲进程,系统启动后运行的第一个进程,没有其他进程运行时会运行该进程,是1号和2号的父进程
- 1进程(init):由0号进程创建,完成一些硬件的必要的初始化工作,收养孤儿进程
- 2进程(kthreadd)进程:调度进程,由0号进程创建,完成任务调度问题
- 孤儿进程:当前进程还在运行,父进程就退出了
- 僵尸进程:当前进程已退出,但父进程没有为其回收资源
进程状态
可以通过
ps -aux查看进程状态
- 主状态:
D:不可中断的休眠状态,通常是IO操作R:运行态S:可中断的休眠状态T:停止态t:调试时的停止X:死亡态Z:僵尸进程I:空闲
- 附加态:
<:高优先级,对其他用户不友好N:低优先级,对其他用户友好L:锁在内存中,不能被放到swap分区中s:会话组的先导进程l:多线程的进程+:前台运行的进程,输入输出都占用终端
进程状态切换
jobs -l:查看当前终端所有停止的进程的作业号和进程号bg 作业号:将暂停的作业在后台运行fg 作业号:将后台运行的作业切换到前台运行
进程五态
创建态,就绪态,执行态,挂起态,死亡态
![[五态转换.jpg]]
多进程实现
新的进程直接拷贝父进程的资源,只改变少部分的数据,保留了父进程大部分数据,拷贝过程通过
fork实现
创建进程
pid_t fork(void);:拷贝父进程得到子进程- 头文件:
<sys/types.h><unistd.h>
- 返回值:创建成功,子进程得到0,父进程得到子进程的进程号;创建失败返回-1并置位错误码
- 头文件:
- 父子进程执行没有固定先后顺序,与抢占时间片先后有关
- 若不管返回值,有\(n\)个
fork,就有\(2^n\)个进程;若在子进程代码块中再创建进程,就是子进程再创建子进程
pid_t pid=fork(); //从fork行开始后面的代码父子进程一样
if(pid>0){ //父进程
wait(nullptr);
}
else if(pid==0) { //子进程
exit(EXIT_SUCCESS);
}
else { //创建失败
perror("fork error");
return -1;
}
替换进程
int execl(const char *path, const char *arg, .../* (char *) NULL */);:用另外一个程序重构用户空间- 头文件:
<unistd.h> - 参数:
- 用于替换的可执行文件的文件名
- 要传给程序的参数:arg0,arg1,arg2...,以
nullptr作为结束标志
- 返回值:失败返回-1并置位错误码
- 头文件:
pid_t pid=fork(); //从fork行开始后面的代码父子进程一样
if(pid>0){ //父进程
wait(nullptr);
}
else if(pid==0) { //子进程
if(execl(文件名,参数...)==-1){ //替换子进程
perror("execl error");
return -1;
}
}
else { //创建失败
perror("fork error");
return -1;
}
获取进程号
pid_t getpid(void);:获取当前进程的进程号- 头文件:
<sys/types.h><unistd.h>
- 返回值:当前进程的进程号
- 头文件:
pid_t getppid(void);:获取父进程的进程号- 头文件:
<sys/types.h><unistd.h>
- 返回值:父进程进程的进程号
- 头文件:
退出进程
void exit(int status);:退出当前进程,库函数,会刷新并关闭标准IO的缓冲区- 头文件:
<stdlib.h>- 参数:退出时的状态,会和0377做与运算后返回给父进程
EXIT_SUCCESS:返回成功,即0EXIT_FAILURE:返回失败,即1
- 参数:退出时的状态,会和0377做与运算后返回给父进程
- 头文件:
void _exit(int status);:退出当前进程,内核函数,不会会刷新并关闭标准IO的缓冲区- 头文件:
<unistd.h> - 参数:退出时的状态,会和0377做与运算后返回给父进程
EXIT_SUCCESS:返回成功,即0EXIT_FAILURE:返回失败,即1
- 头文件:
回收进程资源
pid_t wait(int *wstatus);:阻塞回收子进程的资源,等价于waitpid(-1,wstatus,0)- 头文件:
<sys/types.h><sys/wait.h>
- 参数:子进程退出时,获取子进程退出时的状态与0377做与运算后的结果,可以传
nullptr - 返回值:成功返回回收进程的进程号,失败返回-1并置位错误码
- 头文件:
pid_t waitpid(pid_t pid, int *wstatus, int options);:阻塞或非阻塞回收指定进程的资源- 头文件:
<sys/types.h><sys/wait.h>
- 参数:
- 进程号
- \(\gt 0\):回收指定进程号的进程
- \(= 0\):回收当前相同进程组的子进程
- \(= -1\):回收任意子进程
- \(\lt -1\):回收指定进程组中的子进程,进程组号就是参数的绝对值
- 退出状态:子进程退出时,获取子进程退出时的状态与0377做与运算后的结果,可以传
nullptr - 是否阻塞:
0:阻塞回收WNOHANG:非阻塞回收
- 进程号
- 返回值:
- \(\gt 0\):回收到的子进程的进程号
- \(= 0\):没有回收到子进程
- \(= -1\):回收失败,置位错误码
- 头文件:
进程间通信
多个进程间的用户空间相互独立,无法通过用户区完成通信
可以通过内核空间通信
- IPC:interprocess communication,进程间通信
- 使用内核空间实现多个进程间通信,根据使用的容器或方式不同,分类:
- 内核提供的通信方式(传统):
- 无名管道
- 有名管道
- 信号
- system V提供的通信方式:
- 消息队列
- 共享内存
- 信号量(信号灯集)
- 套接字通信:Socket网络通信(跨主机通信)
- 内核提供的通信方式(传统):
管道
- 管道是一种特殊的文件,不用于存储数据,只用于进程间通信
- 管道分为有名管道和无名管道
- 在内核空间中创建一个管道通信,一个进程可以将数据写入管道,经由管道缓冲到另一个进程读取
管道通信特点:
- 管道可以实现自己和自己通信
- 对管道中数据的操作是一次性的,管道中的数据被读取后就从管道中消失了
- 管道文件的大小:64K
- 对管道的操作只能使用文件IO方式,但不可以
lseek偏移文件光标,必须先进后出(先写后读) - 读写特点:
- 读端存在时:写端写满64k后再写入会阻塞
- 当读端不存在时:写端写满后再向管道中写入数据时,会发生管道破裂,内核空间会向用户空间发送
SIGPIPE信号,进程收到该信号后退出 - 当写端存在时:管道中没有数据后再读取会阻塞
- 当写端不存在时:管道中没有数据后再读取不会会阻塞
- 管道通信是半双工通信方式,两个管道可以实现全双工
- 单工:只能A向B发信息
- 半双工:同一时刻只能A向B发信息
- 全双工:任意时刻,A和B能相互发消息
无名管道
- 在内存中创建,不存在于文件系统,随进程结束而消失
- 仅适用于亲缘进程间通信
API:
int pipe(int pipefd[2]);:创建一个无名管道- 头文件:
<unistd.h> - 参数:整形数组,用于接收无名管道的两个文件描述符
pipefd[0]:读端pipefd[1]:写端
- 返回值:成功返回0,失败返回-1并置位错误码
- 注意:
- 需要在创建子进程前创建管道才能使子进程和父进程通信
- 使用完文件描述符需要将所有读端和所有写端的文件描述符关掉,无名管道才会消失
- 头文件:
实例
#include <myHead.h>
using namespace std;
int main(int argc,char* argv[]){
int fds[2];
if(pipe(fds)==-1){ //创建管道
perror("create pipe error");
return -1;
}
cout<<fds[0]<<" "<<fds[1]<<endl;
pid_t pid=fork();
if(pid>0){ //父进程
close(fds[0]);
char buff[128]="hello world";
write(fds[1],buff,strlen(buff));
close(fds[1]);
wait(nullptr);
}
else if(pid==0){ //子进程
close(fds[1]);
char buff[128]="";
read(fds[0],buff,sizeof(buff)-1);
cout<<buff<<endl;
close(fds[0]);
exit(EXIT_SUCCESS);
}
else {
perror("fork error");
return -1;
}
return 0;
}
有名管道
- 会在文件系统中创建一个管道文件
- 可以实现任意进程间通信
API:
int mkfifo(const char *pathname, mode_t mode);:创建一个管道文件- 头文件:
<sys/types.h><sys/stat.h>
- 参数:
- 管道文件名
- 管道文件权限,最终新文件的权限为
mode&(~umask)
- 返回值:成功返回0,失败返回-1并置位错误码
- 注意:管道文件创建后,其他进程可以对管道文件打开读写,必须要保证管道文件的两端都没有关闭才能执行读写操作,否则会在
open阻塞
- 头文件:
实例
#include <myHead.h>
using namespace std;
int main(int argc,const char* argv[]){
if(mkfifo("./myfifo",0644)==-1){
perror("mkfifo errir");
return -1;
}
cout<<"create fifo successfully"<<endl;
return 0;
}
#include <myHead.h>
using namespace std;
int main(int argc,const char* argv[]){
int sfd=-1;
if((sfd=open("./myfifo",O_WRONLY))==-1){
perror("send open error");
return -1;
}
char wbuf[128]="";
while(true){
fputs("输入:\n",stdout);
fgets(wbuf,sizeof(wbuf)-1,stdin);
wbuf[strlen(wbuf)-1]='\0';
write(sfd,wbuf,strlen(wbuf));
if(strcmp(wbuf,"quit")==0)break;
}
close(sfd);
return 0;
}
#include <myHead.h>
using namespace std;
int main(int argc,const char* argv[]){
int rfd=-1;
if((rfd=open("./myfifo",O_RDONLY))==-1){
perror("send open error");
return -1;
}
char rbuf[128]="";
while(true){
memset(rbuf,0,sizeof(rbuf));
read(rfd,rbuf,sizeof(rbuf)-1);
fprintf(stdout,"received:%s\n",rbuf);
if(strcmp(rbuf,"quit")==0)break;
}
close(rfd);
return 0;
}
信号
信号只能实现多个进程间通知作用,不能实现数据传输
- 信号是软件模拟硬件的中断功能,信号由软件实现,中断由硬件实现
- 中断:停止当前正在执行的事情去做另一件事情
- 用户,内核,进程都可以给进程发送信号
- 用户:如输入
Ctrl+C - 内核:如管道破裂时发送
SIGPIPE - 进程:通过相关函数实现
- 用户:如输入
- 信号通信属于异步通信
- 同步:多个任务有执行先后顺序
- 异步:多个任务没有执行先后顺序
- 对信号的处理方式:捕获,忽略,默认
- 执行信号处理函数期间,系统默认屏蔽该信号
绑定处理方式
sighandler_t signal(int signum, sighandler_t handler):将信号与信号处理方式绑定到一起- 头文件:
<signal.h> - 参数:
- 要处理的信号
SIGKILL和SIGSTOP是不合法的参数
- 处理方式:
SIG_IGN:忽略SIG_DFL:默认,一般默认都是杀死进程typedef void (*sighandler_t)(int);函数指针,用户自定义,参数是要处理的信号
- 要处理的信号
- 返回值:成功返处理方式的起始地址,失败返回
SIG_ERR并置位错误码 - 注意:只要信号与信号处理方式绑定以后,收到信号后处理方式会立即响应
- 头文件:
#include <myHead.h>
using namespace std;
int main(int argc,const char* argv[]){
auto handler=[](int signum){
if(signum==SIGCHLD){
//由于生产僵尸进程速度过快,需要循环非阻塞回收
while(waitpid(-1,nullptr,WNOHANG)>0);
}
};
if(signal(SIGCHLD,handler)==SIG_ERR){
perror("signal error");
return -1;
}
//创建多个僵尸进程
for(int i=0;i<5;i++){
if(fork()==0){ //创建子进程后立即退出子进程
exit(EXIT_SUCCESS);
}
}
while(true);
return 0;
}
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);:重新设置信号处理方式- 参数:
- 信号,可以是
SIGKILL和SIGSTOP - 新的处理方式
- 用于保存旧的处理方式,可以为空
- 信号,可以是
- 返回值:成功返回0,失败返回-1并置位错误码
sigaction结构体:
struct sigaction { void(*sa_handler)(int); //信号处理函数 void(*sa_sigaction)(int, siginfo_t *, void *); //信号处理函数,可以获得信号的详细信息 sigset_t sa_mask; //指定处理信号期间需要屏蔽的信号 int sa_flags; //指定信号处理行为 void (*sa_restorer)(void); //一般不用 };sa_flags:SA_RESTART,使被信号打断的系统调用自动重新发起SA_NOCLDSTOP,使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号SA_NOCLDWAIT,使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程SA_NODEFER,使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号SA_RESETHAND,信号处理之后重新设置为默认的处理方式SA_SIGINFO,使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数
- 参数:
int sigfillset(sigset_t *set);:用所有信号初始化set
发送信号
int kill(pid_t pid, int sig);:向指定进程或进程组发送信息- 头文件:
<signal.h> - 参数:
- 进程号或进程组号
- \(\gt 0\):向指定进程发信号
- \(=0\):向同进程组中的所有进程发信号
- \(=-1\):向所有进程发信号
- \(\lt -1\):向指定进程组发信号,进程组id为参数的绝对值
- 要发送的信号
- 进程号或进程组号
- 返回值:成功返回0,失败返回-1并置位错误码
- 头文件:
int raise(int sig);:向自己发送信号,等价于kill(getpid(),sig)- 头文件:
<signal.h> - 参数:要发送的信号
- 返回值:成功返回0,失败返回非0值
- 头文件:
unsigned int alarm(unsigned int seconds);:在指定时间后向调用者进程发送SIGALRM信号- 参数:指定秒数,若为0表示取消之前设置的所有alarm
- 返回值:成功时返回先前设置的alarm剩余的时间,若先前没有alarm返回0
实例
#include <myHead.h>
using namespace std;
int main(int argc,const char* argv[]){
auto handler=[](int signum){
if(signum==SIGUSR1){
wait(nullptr);
cout<<"son of a bitch"<<endl;
raise(SIGKILL);
}
};
if(signal(SIGUSR1,handler)==SIG_ERR){ //绑定信号和处理方式
perror("signal error");
return -1;
}
pid_t pid=fork();
if(pid>0){ //父进程
while(true){
cout<<"mofei daisuki"<<endl;
sleep(1);
}
}
else if(pid==0){ //子进程
sleep(5);
cout<<"die with me"<<endl;
kill(getppid(),SIGUSR1); //向父进程发送信号
exit(EXIT_SUCCESS);
}
else {
perror("fork error");
return -1;
}
return 0;
}
IPC对象
- 消息队列,共享内存,信号量都是借助内核空间完成的
- 在内核空间中创建出相关的对象容器,进行进程间通信时,将信息放入对象容器中,另一个进程从容器中取数据
- system V的IPC对象实现了数据传输的容器和程序的分离,程序结束后容器中的数据依然存在,除非手动删除
system V提供的进程间通信IPC对象相关指令:
ipcs [选项]:查看所有信息-q:查看消息队列信息-m:查看共享内存信息-s:查看信号量信息
ipcrm [选项] id:删除指定IPC对象-q:删除消息队列信息-m:删除共享内存信息-s:删除信号量信息
创建key值
key_t ftok(const char *pathname, int proj_id);:通过给定文件路径和随机数创建一个4字节整数的key值,用于system V的IPC对象的创建- 头文件:
<sys/types.h><sys/ipc.h>
- 参数:key值由参数组成
- 一个已存在的文件路径,提供key值3字节的内容
- 文件设备号占1字节
- 文件incode号占2字节
- 一个非零随机整数,取后8位,占key值的1字节
- 一个已存在的文件路径,提供key值3字节的内容
- 返回值:成功返回key值,失败返回-1并置位错误码
- 头文件:
消息队列
- 对消息队列中的消息的读取是一次性的
- 消息队列大小:16K
创建&打开
int msgget(key_t key, int msgflg):通过给定的key值创建/打开消息队列并返回消息队列id- 头文件:
<sys/types.h><sys/ipc.h><sys/msg.h>
- 参数:
- key值:
IPC_PRIVATE:只用于亲缘进程间通信ftok创建出来的key值,可以用于任意进程间通信
- 创建标识:多个标识用
|隔开IPC_CREAT:创建并打开一个消息队列,若消息队列已存在,直接打开IPC_EXCL:确保要创建一个消息队列,若消息队列已存在,报错,错误码为EEXIST- 权限:消息队列操作的权限,如
0644
- key值:
- 返回值:成功返回消息队列id,失败返回-1并置位错误码
- 头文件:
发送消息
发送信息需要对消息队列有写权限
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);:向消息队列存放一个消息- 头文件:
<sys/types.h><sys/ipc.h><sys/msg.h>
- 参数:
- 消息队列id
- 要发送的消息的起始地址,一般定义为一个结构体
struct mymsg { long mtype; //消息类型,只能为正整数 //后面是消息正文 }; - 消息结构体中正文的大小
- 标识是否阻塞
0:阻塞形式向消息队列中存放信息,消息队列满了会在该函数处阻塞IPC_NOWAIT:非阻塞形式向消息队列中存放信息,消息队列满了直接返回
- 返回值:成功返回0,失败返回-1并置位错误码
- 头文件:
接收消息
接收消息需要对消息队列有读权限
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);:从消息队列中取出一个消息- 头文件:
<sys/msg.h> - 参数:
- 消息队列id
- 接收消息的起始地址,一般定义为一个结构体
- 消息结构体中正文的大小
- 要接收的消息的类型
- \(=0\):读取消息队列中队首的消息,无论消息类型
- \(\gt 0\):读取消息队列中第一个
msgtyp类型的消息 - \(\lt 0\):读取消息队列中第一个绝对值小于
msgtyp的类型
- 标识是否阻塞
0:阻塞形式从消息队列中读取消息,消息队列空了会在该函数处阻塞IPC_NOWAIT:非阻塞形式从消息队列中读取信息,消息队列空了直接返回
- 返回值:成功读取返回实际读取的正文大小,失败返回-1并置位错误码
- 头文件:
控制
int msgctl(int msqid, int cmd, struct msqid_ds *buf);:对指定的消息队列执行相关操作,具体由cmd参数决定- 头文件:
<sys/types.h><sys/ipc.h><sys/msg.h>
- 参数:
-
消息队列id
-
要执行的操作
IPC_STAT:获取当前消息队列的属性,参数3用于存放消息队列的属性IPC_SET:设置当前消息队列的属性,参数3是要设置的消息队列的属性的结构体起始地址IPC_RMID:销毁消息队列
-
消息队列属性结构体地址:若
cmd为IPC_RMID,传nullptrstruct msqid_ds { struct ipc_perm msg_perm; //消息队列所有者和权限 time_t msg_stime; //最后一次发送消息时间 time_t msg_rtime; //最后一次接收消息时间 time_t msg_ctime; //最后一次状态更改时间 unsigned long __msg_cbytes; //已用字节数 msgqnum_t msg_qnum; //消息个数 msglen_t msg_qbytes; //最大消息队列大小 pid_t msg_lspid; //最后一次发送消息的进程号 pid_t msg_lrpid; //最后一次接收消息的进程号 };struct ipc_perm { key_t __key; //key值 uid_t uid; //当前进程的uid gid_t gid; //当前进程的gid uid_t cuid; //消息队列创建者的uid gid_t cgid; //消息队列创建者的gid unsigned short mode; //消息队列的权限 unsigned short __seq; //队列号 };
-
- 返回值:成功返回0,失败返回-1并置位错误码
- 头文件:
实例
#include <myHead.h>
using namespace std;
struct msgbuf_{
long mtype;
char msg[1024];
};
int main(int argc,const char* argv[]){
//创建key值
key_t key=ftok("/",'m');
if(key==-1){
perror("ftok error");
return -1;
}
printf("key=%x\n",key);
//创建,打开消息队列
int mqid=msgget(key,IPC_CREAT|0644);
if(mqid==-1){
perror("msgget error");
return -1;
}
cout<<"mqid="<<mqid<<endl;
//发送消息
msgbuf_ buff;
const size_t msgsize=sizeof(buff.msg);
while(true){
printf("input message type:");
scanf("%ld", &buff.mtype);
getchar();
printf("input message text:");
fgets(buff.msg, msgsize, stdin);
buff.msg[strlen(buff.msg)-1] = 0;
if(msgsnd(mqid, &buff, msgsize, 0)==-1){
perror("msgsnd error");
return -1;
}
if(strcmp(buff.msg,"quit")==0){
break;
}
}
return 0;
}
#include <myHead.h>
using namespace std;
struct msgbuf_{
long mtype;
char msg[1024];
};
int main(int argc,const char* argv[]){
//创建key值
key_t key=ftok("/",'m');
if(key==-1){
perror("ftok error");
return -1;
}
printf("key=%x\n",key);
//创建,打开消息队列
int mqid=msgget(key,IPC_CREAT|0644);
if(mqid==-1){
perror("msgget error");
return -1;
}
cout<<"mqid="<<mqid<<endl;
//接收消息
msgbuf_ buff;
const size_t msgsize=sizeof(buff.msg);
while(true){
memset(&buff,0,sizeof(buff));
if(msgrcv(mqid,&buff,msgsize,0,0)==-1){
perror("msgrcv error");
return -1;
}
printf("received:\t%s\n",buff.msg);
if(strcmp(buff.msg,"quit")==0)break;
}
//销毁消息队列
if(msgctl(mqid,IPC_RMID,nullptr)==-1){
perror("msgctl error");
return -1;
}
return 0;
}
共享内存
- 通过一段物理内存在内核区中映射出一个共享内存段,不同进程通过共享内存段的起始地址向共享内存段读写数据
- 使用时可能产生竞态,为了解决这个问题,共享内存一般会跟信号量一起使用,完成进程的同步功能
- 消息队列保证数据不丢失,共享内存保证数据的时效性
- 对共享内存的数据的读取不是一次性的
- 使用共享内存时,不需要从用户空间切换到内核空间,是所有进程间通信方式中效率最高的一种通信方式
创建&打开
int shmget(key_t key, size_t size, int shmflg);:申请指定大小的物理内存,映射到内核空间,创建出共享内存段- 头文件:
<sys/ipc.h><sys/shm.h>
- 参数:
- key值:
IPC_PRIVATE:只用于亲缘进程间通信ftok创建出来的key值,可以用于任意进程间通信
- 申请的共享内存段的大小,是
PAGE_SIZE即一页(4026字节)的整数倍,向上取整 - 创建标识:多个标识用
|隔开IPC_CREAT:创建并打开一个消息队列,若消息队列已存在,直接打开IPC_EXCL:确保要创建一个消息队列,若消息队列已存在,报错,错误码为EEXIST- 权限:共享内存操作的权限,如
0644
- key值:
- 返回值:成功返回共享内存id,失败返回-1并置位错误码
- 头文件:
映射
void *shmat(int shmid, const void *shmaddr, int shmflg);:将共享内存段映射到用户空间- 头文件:
<sys/types.h><sys/shm.h>
- 参数:
- 共享内存id
- 物理内存的起始地址,一般传
nullptr,由系统自动选择一个合适的对齐页 - 对共享内存的操作:
0:可读可写SHM_RDNOLY:只读
- 返回值:成功返回共享内存的起始地址,失败返回
(void*)-1并置位错误码
- 头文件:
释放映射关系
int shmdt(const void *shmaddr);:释放进程于共享内存的映射关系- 头文件:
<sys/types.h><sys/shm.h>
- 参数:共享内存的起始地址
- 返回值:成功返回0,失败返回-1并置位错误码
- 头文件:
控制
int shmctl(int shmid, int cmd, struct shmid_ds *buf);:对指定的共享内存执行相关操作,具体由cmd参数决定- 头文件:
<sys/ipc.h><sys/shm.h>
- 参数:
-
共享内存段id
-
要执行的操作
IPC_STAT:获取当前共享内存的属性,参数3用于存放共享内存的属性IPC_SET:设置当前共享内存的属性,参数3是要设置的共享内存的属性的结构体起始地址IPC_RMID:销毁共享内存
-
共享内存属性结构体地址:若
cmd为IPC_RMID,传nullptrstruct shmid_ds { struct ipc_perm shm_perm; /* Ownership and permissions */ size_t shm_segsz; /* Size of segment (bytes) */ time_t shm_atime; /* Last attach time */ time_t shm_dtime; /* Last detach time */ time_t shm_ctime; /* Last change time */ pid_t shm_cpid; /* PID of creator */ pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */ shmatt_t shm_nattch; /* No. of current attaches */ ... };
-
- 返回值:成功返回0,失败返回-1并置位错误码
- 头文件:
实例
#include <myHead.h>
using namespace std;
#define PAGE_SIZE 4096
int main(int argc,const char* argv[]){
//创建key值
key_t key=ftok("/",'m');
if(key==-1){
perror("ftok error");
return -1;
}
printf("key=%x\n",key);
//创建,打开共享内存
int size=PAGE_SIZE;
int shmid=shmget(key,size,IPC_CREAT|0644);
if(shmid==-1){
perror("shmget error");
return -1;
}
//映射
char* addr=(char*)shmat(shmid,nullptr,0);
if(addr==(void*)-1){
perror("shmat error");
return -1;
}
printf("addr=%p\n",addr);
//写入
while(true){
fputs("input:\n",stdout);
fgets(addr,size,stdin);
addr[strlen(addr)-1]=0;
if(strcmp(addr,"quit")==0){
break;
}
}
//取消映射
if(shmdt(addr)==-1){
perror("shmdt error");
return -1;
}
return 0;
}
#include <myHead.h>
using namespace std;
#define PAGE_SIZE 4096
int main(int argc,const char* argv[]){
//创建key值
key_t key=ftok("/",'m');
if(key==-1){
perror("ftok error");
return -1;
}
printf("key=%x\n",key);
//创建共享内存
int size=PAGE_SIZE;
int shmid=shmget(key,size,IPC_CREAT|0644);
if(shmid==-1){
perror("shmget error");
return -1;
}
//映射
char* addr=(char*)shmat(shmid,nullptr,0);
if((void*)addr==(void*)-1){
perror("shmat error");
return -1;
}
printf("addr=%p\n",addr);
//读取
while(true){
sleep(1);
printf("received: %s\n",addr);
if(strcmp(addr,"quit")==0){
break;
}
}
//取消映射
if(shmdt(addr)==-1){
perror("shmdt error");
return -1;
}
//销毁共享内存段
if(shmctl(shmid,IPC_RMID,nullptr)==-1){
perror("shmctl error");
return -1;
}
return 0;
}
信号量(信号灯集)
- 本质上维护一个信号量数组,信号量是一个整数
- 一个进程向一个信号量申请资源
- 若信号量资源大于0,进程获得资源,执行进程后面的语句,对应的信号量减小
- 若信号量资源等于0,进程可以阻塞等待,直到获得资源
- 一个进程释放资源到一个信号量,这个信号量增加
- 信号量实现多个进程间同步问题,一般不进行通信
创建&打开
int semget(key_t key, int nsems, int semflg);:创建信号灯集- 头文件:
<sys/types.h><sys/ipc.h><sys/sem.h>
- 参数:
- key值
- 信号量个数
- 创建标识:多个标识用
|隔开IPC_CREAT:创建并打开一个信号灯集,若信号灯集已存在,直接打开IPC_EXCL:确保要创建一个信号灯集,若信号灯集已存在,报错,错误码为EEXIST- 权限:信号灯集操作的权限,如
0644
- 返回值:成功返回信号灯集id,失败返回-1并置位错误码
- 头文件:
操作
int semop(int semid, struct sembuf *sops, size_t nsops);:P(申请资源)V(释放资源)操作- 头文件:
<sys/types.h><sys/ipc.h><sys/sem.h>
- 参数:
- 信号灯集id
- 有关操作的结构体
struct sembuf{ unsigned short sem_num; //要操作的信号量编号,即下标 short sem_op; //要进行的操作,大于0释放资源,小于0申请资源 short sem_flg; //操作标识,0阻塞,IPC_NOWAIT非阻塞 } - 要操作的信号量个数
- 返回值:成功返回0,失败返回-1并置位错误码
- 头文件:
控制
int semctl(int semid, int semnum, int cmd, ...);:对指定的信号灯集执行相关操作,具体由cmd参数决定- 头文件:
<sys/types.h><sys/ipc.h><sys/sem.h>
- 参数:
- 信号灯集id
- 要控制的信号量编号(下标),编号从0开始
- 要执行的操作
IPC_RMID:销毁信号灯集,参数2忽略,参数4可以不传SETVAL:对参数2对应的信号量设置值GETVAL:对参数2对应的信号量获取值SETALL:对所有信号量设置值GETALL:对所有信号量获取值IPC_STAT:获取当前信号灯集的属性IPC_SET:设置当前信号灯集的属性
- 是一个共用体,根据不同的参数3,传入不同的值
union semun { int val; //SETVAL要设置的值 struct semid_ds *buf; //对于IPC_STAT,IPC_SET unsigned short *array; //对于GETALL,SETALL struct seminfo *__buf; /* Buffer for IPC_INFO(Linux-specific) */ };
- 返回值:成功时,
GETVAL返回对应值,其他返回0;失败返回-1并置位错误码
- 头文件:
封装API
封装为信号灯集的创建,销毁,申请资源,释放资源
#pragma once
#include <myHead.h>
union semun {
int val; //SETVAL要设置的值
struct semid_ds* buf; //对于IPC_STAT,IPC_SET
unsigned short* array; //对于GETALL,SETALL
struct seminfo* __buf; /* Buffer for IPC_INFO(Linux-specific) */
};
class Sem {
public:
bool setVal(int index);
Sem(key_t key, int count);
bool P(int index); //申请资源
bool V(int index); //释放资源
bool deleteSem(); //销毁信号灯集
private:
int semID;
int semCount; //信号灯个数
};
#include "Sem.h"
Sem::Sem(key_t key, int count) {
semCount = count;
//打开信号灯集
semID = semget(key, count, IPC_CREAT | IPC_EXCL | 0644);
if (semID == -1) {
if (errno == EEXIST) {
semID = semget(key, count, IPC_CREAT | 0644); //若信号灯集已存在,直接打开,不需要初始化
if (semID == -1) {
perror("semget error");
}
}
}
//第一次打开时初始化
else {
for (int i = 0;i < semCount;i++) {
setVal(i);
}
}
}
bool Sem::setVal(int index) {
int val = -1;
printf("set value of sem whose index is %d: ", index);
scanf("%d", &val);
getchar(); //把换行符吃了
semun su;
su.val = val;
if (semctl(semID, index, SETVAL, su) == -1) {
perror("semctl error");
return false;
} else return true;
}
bool Sem::P(int index) {
sembuf sb;
sb.sem_num = index;
sb.sem_op = -1;
sb.sem_flg = 0;
if (semop(semID, &sb, 1) == -1) {
perror("semop error");
return false;
} else return true;
}
bool Sem::V(int index) {
sembuf sb;
sb.sem_num = index;
sb.sem_op = 1;
sb.sem_flg = 0;
if (semop(semID, &sb, 1) == -1) {
perror("semop error");
return false;
} else return true;
}
bool Sem::deleteSem() {
if (semctl(semID, 0, IPC_RMID) == -1) {
perror("delete error");
return false;
}
return true;
}
实例
实现共享内存的同步
#include <myHead.h>
#include "Sem.h"
using namespace std;
#define PAGE_SIZE 4096
int main(int argc, const char* argv[]) {
//创建信号灯集
key_t key_sem = ftok("/", 'o');
Sem s(key_sem, 2);
//创建共享内存的key值
key_t key = ftok("/", 'm');
if (key == -1) {
perror("ftok error");
return -1;
}
printf("key=%x\n", key);
//创建,打开共享内存
int size = PAGE_SIZE;
int shmid = shmget(key, size, IPC_CREAT | 0644);
if (shmid == -1) {
perror("shmget error");
return -1;
}
//映射
char* addr = (char*)shmat(shmid, nullptr, 0);
if (addr == (void*)-1) {
perror("shmat error");
return -1;
}
printf("addr=%p\n", addr);
//写入
while (true) {
//申请0号信号量
s.P(0);
fputs("input:\n", stdout);
fgets(addr, size, stdin);
addr[strlen(addr) - 1] = 0;
//释放1号信号量
s.V(1);
if (strcmp(addr, "quit") == 0) {
break;
}
}
//取消映射
if (shmdt(addr) == -1) {
perror("shmdt error");
return -1;
}
return 0;
}
#include <myHead.h>
#include "Sem.h"
using namespace std;
#define PAGE_SIZE 4096
int main(int argc, const char* argv[]) {
//创建信号灯集
key_t key_sem = ftok("/", 'o');
Sem s(key_sem, 2);
//创建key值
key_t key = ftok("/", 'm');
if (key == -1) {
perror("ftok error");
return -1;
}
printf("key=%x\n", key);
//创建共享内存
int size = PAGE_SIZE;
int shmid = shmget(key, size, IPC_CREAT | 0644);
if (shmid == -1) {
perror("shmget error");
return -1;
}
//映射
char* addr = (char*)shmat(shmid, nullptr, 0);
if ((void*)addr == (void*)-1) {
perror("shmat error");
return -1;
}
printf("addr=%p\n", addr);
//读取
while (true) {
//申请1号信号量
s.P(1);
printf("received: %s\n", addr);
//释放0号信号量
s.V(0);
if (strcmp(addr, "quit") == 0) {
break;
}
}
//取消映射
if (shmdt(addr) == -1) {
perror("shmdt error");
return -1;
}
//销毁信号灯集
s.deleteSem();
//销毁共享内存段
if (shmctl(shmid, IPC_RMID, nullptr) == -1) {
perror("shmctl error");
return -1;
}
return 0;
}

浙公网安备 33010602011771号