Linux进程间通信简介
进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。
IPC 的方式通常有管道(包括无名管道和命名管道)、消息队列、共享内存、信号、信号量、Socket、Streams 等。其中 Socket 和 Streams 支持不同主机上的两个进程 IPC。
管道
无名管道(pipe)
管道,通常指无名管道,是 UNIX 系统IPC最古老的形式。
特点:
1、它是半双工的(即数据只能在一个方向上流动),具有固定的读端(fd[0])和写端(fd[1])。
2、它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。
3、它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
4、管道中的数据读走就没了。
原型:
1 #include <unistd.h>
2 int pipe(int fd[2]); // 返回值:若成功返回0,失败返回-1
当一个管道建立时,它会创建来个文件描述符,fd[0]是为了读而打开,fd[1]是为了写而打开,要关闭管道只需要关闭两个文件描述符即可。
管道属于特殊文件,无法使用lseek函数获取管道中有多少字节的数据,并且在子进程在读管道数据时会堵塞进程,直到父进程向管道内写入数据
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
int fd[2];
pid_t pid;
char readBuf[128];
int nwrite;
int nread;
// int pipe(int pipefd[2]);
if(pipe(fd) == -1){
printf("创建管道失败\n");
}
pid=fork();//创建子进程
if(pid == -1){
printf("创建子进程失败\n");
//1.在父进程写入
}else if(pid > 0){
printf("这是父进程\n");
close(fd[0]);//关闭读文件
//ssize_t write(int fd, const void *buf, size_t count);
nwrite=write(fd[1],"hello from father process",strlen("hello from father process"));
wait(NULL);
//2.在子进程读
}else{
printf("这是子进程\n");
close(fd[1]);
//ssize_t read(int fd, void *buf, size_t count);
nread=read(fd[0],readBuf,128);//如果度端没有内容,会阻塞等待
printf("来自父进程的写入内容是:%s\n",readBuf);
exit(0);
}
return 0;
}
注意: 管道为半双工通信,读和写操作在同一时间内只能进行一个,所以在读的时候要关闭写端,写的时候关闭读端。
有名管道(fifo)
FIFO,也称为命名管道,它是一种文件类型
特点
FIFO可以在无关的进程之间交换数据,与无名管道不同。
FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
原型
1 #include <sys/stat.h>
2 // 返回值:成功返回0,出错返回-1
3 int mkfifo(const char *pathname, mode_t mode);
pathname:
当前路径下要生成的fifo文件名
mode:
=0600为可读可写 4可读 2可写 1可执行 所以0600为可读可写权限
返回值:成功返回0,失败返回-1;
fiforead.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
int nread;
mkfifo("./file",0600);
char buf[1024] = {0};
int fd = open("./file",O_RDONLY);//若没有指定O_NONBLOCK(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO
printf("open success\n");
while(1){
nread = read(fd,buf,1024);
printf("read %d byte ,neirong:%s\n",nread,buf);
}
return 0;
}
fifowrite.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
mkfifo("./file",0600);
char *str = "this is a fifo demo!";
int fd = open("./file",O_WRONLY);
printf("open success\n");
while(1){
write(fd,str,strlen(str));
sleep(1);
}
return 0;
}
运行read会阻塞,一直到运行write后read才会继续往下执行,数据读出时管道数据清除。
消息队列(msg)
息队列,是消息的链接表(链表结构),存放在Linux内核中,一个消息队列由一个标识符(队列ID)来标识
特点:
1、消息队列是面向记录的,其中的消息具有特定的格式和特定的优先级
2、消息队列独立于接收和发送进程,当进程终止时,消息队列及其内容并不会被删除
3、消息队列可以实现随机查询,消息不一定要以先入先出的方式读取,也可以按照消息的类型读取
API原型
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
1、创建或打开消息队列
int msgget(key_t key, int msgflg);
函数作用:创建或打开消息队列key:由ftok()函数获得
msgflg:IPC_CREAT|0777代表没有这个消息队列则创建,权限为可读可写可执行
返回值:成功返回消息队列ID(非负整数),失败返回-1;
2、添加消息
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
函数作用:添加消息队列数据msqid:消息队列ID,由msgget()函数获得
msgp:是一个结构体指针,指向struct msgbuf
struct msgbuf {
long mtype; /* message type, must be > 0 */ 消息类型,必须大于0
char mtext[1]; /* message data */ 消息数据
};msgsz:消息的长度,一般用sizeof()关键字或strlen()函数来计算
msgflg:只有在消息队列满或队列达到系统范围限制才有效该参数才有效,一般设置为0
返回值:成功返回0,失败返回-1
3、接收消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
函数作用:接收消息队列数据msqid:消息队列ID,由msgget()函数获得
msgp:是一个结构体指针,指向struct msgbuf
struct msgbuf {
long mtype; /* message type, must be > 0 */ 消息类型,必须大于0
char mtext[1]; /* message data */ 消息数据
};msgsz:消息的长度,一般用sizeof()关键字或strlen()函数来计算
msgtyp:消息类型
msgflg:只有在消息队列满或队列达到系统范围限制才有效该参数才有效,一般设置为0
返回值:成功返回消息数据长度,失败返回-1
4、控制消息队列int msgctl(int msqid, int cmd, struct msqid_ds *buf);
函数作用:控制消息队列
msqid:消息队列ID,由msgget()函数获得
cmd:设置为IPC_RMID代表立即删除消息队列
buf:配合cmd参数一起使用,无需使用时设置为NULL
返回值:成功返回0,失败返回-1
获取key值
#include <sys/types.h>
#include <sys/ipc.h>key_t ftok(const char *pathname, int proj_id);
//函数作用:将一个已存在的路径名(该路径一定要存在)和一个整数转换为一个key值(这个key是独一无二,它不用和其他共享内存的key相同)。这个key是会被设置进维护共享内存的数据结构当中。
//pathname:路径名
//proj_id:整数,随意
//返回值:失败返回-1,成功返回key键值
msgread.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct msgbuf
{
long mtype;
char mtext[128];
};
int main()
{
struct msgbuf readBuf;
struct msgbuf sendBuf = {999,"i have received"};
key_t key;
key = ftok(".",'z');
printf("key = %x\n",key);
int msgId = msgget(key,IPC_CREAT|0777);
if(msgId == -1){
puts("creat failed\n");
}
msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);
printf("读取来自队列的内容:%s\n",readBuf.mtext);
msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);
printf("发送完毕。\n");
msgctl(msgId,IPC_RMID,NULL);
return 0;
}
msgwrite.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct msgbuf
{
long mtype;
char mtext[128];
};
int main()
{
struct msgbuf sendBuf = {888,"this is msg from que"};
struct msgbuf readBuf;
key_t key;
key = ftok(".",'z');
printf("key = %x\n",key);
int msgId = msgget(key,IPC_CREAT|0777);
if(msgId == -1){
puts("creat failed\n");
}
msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);
printf("发送完毕\n");
msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),999,0);
printf("读取来自队列的内容:%s\n",readBuf.mtext);
msgctl(msgId,IPC_RMID,NULL);
return 0;
}
共享内存(shm)
共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。
特点
- 共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
- 因为多个进程可以同时操作,所以需要进行同步。
信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问
和消息队列的区别:
消息队列就像两个人聊天,一方要将要说的话写在一张纸上放入箱子里,另外一个人去箱子里取出来阅读。这个箱子(消息队列)不会自行销毁,要调用msgctl才可以。
共享内存就好像两个学生在上课的时候,由于不能说话,就只好拿一个本子来聊天,这个本子就相当于共享的内存,双方可以同时看到纸上的内容。比消息队列效率高。调用shmctl删除共享内存。
流程:创建-映射-(操作)-释放-关闭
原型
#include <sys/shm.h>
int shmget(key_t key,size_t size,int flag);
//函数作用:创建或获取一个共享内存
//key:由ftok生成的key标识,标识系统的唯一IPC资源。
//size:需要申请共享内存的大小。在操作系统中,申请内存的最小单位为页,一页是4k字节,为了避免内存碎片,我们一般申请的内存大小为页的整数倍,一般而言是4096的整数倍,因为内存的块的大小就是4KB即4096B
//flag:如果要创建新的共享内存,需要使用IPC_CREAT,可配选项0666代表可读可写
//返回值:成功返回共享内存ID,失败返回-1void *shmat(int shm_id,const void *shmaddr,int flag);
//函数作用:连接共享内存到当前进程的地址空间,不同的进程通过此函数,挂载到同一片内存上
//shm_id:共享存储段的标识符(共享内存ID)
//shmaddr:设置为0,代表让Linux内核为进程自动分配一个内存地址(推荐使用)//flag:设置为0,代表分配的内存为可读可写的。设置为SHM_RDONLY,代表映射的内存只读
//返回值:成功返回共享存储段的指针(虚拟地址),并且内核将使其与该共享存储段相关的shmid_ds结构中的shm_nattch计数器加1(类似于引用计数); 出错返回-1。
int shmdt(void *shmaddr);
//函数作用:断开与共享内存的连接,该函数并不删除所指定的共享内存区,而是将之前用shmat函数连接好的共享内存区脱离目前的进程。
//shmaddr:调用shmat函数连接成功返回的地址
//返回值:成功返回0,并将shmid_ds结构体中的 shm_nattch计数器减1;出错返回-1。
int shmctl(int shm_id,int cmd,struct shmid_ds *buf);
//函数作用:控制共享内存的相关信息
//shm_id:共享存储段的标识符(共享内存ID)
//cmd:指定的执行操作,设置为IPC_RMID表示立即删除共享内存
//buf:设置为NULL即可
//返回值:成功返回0,失败返回-1
获取key值
#include <sys/types.h>
#include <sys/ipc.h>key_t ftok(const char *pathname, int proj_id);
//函数作用:将一个已存在的路径名(该路径一定要存在)和一个整数转换为一个key值(这个key是独一无二,它不用和其他共享内存的key相同)。这个key是会被设置进维护共享内存的数据结构当中。
//pathname:路径名
//proj_id:整数,随意
//返回值:失败返回-1,成功返回key键值
查看系统中有哪些共享内存
指令:ipcs -m
删除共享内存
指令:ipcrm -m 884739 //红字为共享内存ID
shmsend.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
key_t key;
char *shmaddr;
// key_t ftok(const char *pathname, int proj_id);
key=ftok(".",2);
printf("key = %x\n",key);
//int shmget(key_t key, size_t size, int shmflg);
//创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmId=shmget(key,1024*4,IPC_CREAT|0666);//共享内存大小必须以字节为单位
if(shmId == -1){
printf("创建共享内存失败\n");
exit(-1);//异常退出
}
//void *shmat(int shmid, const void *shmaddr, int shmflg);
//连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
shmaddr=shmat(shmId,0,0);//挂载映射,如果引用一个已存在的共享内存(上方改革创建),则将 size 指定为0
printf("shmat ok\n");
strcpy(shmaddr,"hello sharemessage");
sleep(5);
//int shmdt(const void *shmaddr);
// 断开与共享内存的连接
shmdt(shmaddr);
//int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//控制共享内存的相关信息:成功返回0,失败返回-1
shmctl(shmId,IPC_RMID,0);//IPC_RMID删除消息队列
printf("退出\n");
return 0;
}
shmrcv.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
int main()
{
key_t key;
char *shmaddr;
// key_t ftok(const char *pathname, int proj_id);
key=ftok(".",2);
printf("key = %x\n",key);
//int shmget(key_t key, size_t size, int shmflg);
//创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmId=shmget(key,1024*4,0);//只要打开就行不必创建
if(shmId == -1){
printf("创建共享内存失败\n");
exit(-1);//异常退出
}
//void *shmat(int shmid, const void *shmaddr, int shmflg);
//连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
shmaddr=shmat(shmId,0,0);//挂载映射,如果引用一个已存在的共享内存,则将 size 指定为0
printf("shmat ok\n");
printf("内容是:%s\n",shmaddr);
//int shmdt(const void *shmaddr);
//断开与共享内存的连接
shmdt(shmaddr);
return 0;
}
信号(signal)
优质博文参考:https://www.jianshu.com/p/f445bfeea40a
概述
1.信号:对Linux来说就是软中断,与单片机的硬件中断(串口)类似。如在linux中输入 ctrl+c 来停止一个程序
2.信号的名字与编号:可在linux中通过 kill -l 查询(Linux系统一共有64个信号,编号1-64。不存在0信号,0信号有特殊的应用:在系统级的应用中被占用)2.信号的名字与编号:可在linux中通过 kill -l 查询(Linux系统一共有64个信号,编号1-64。不存在0信号,0信号有特殊的应用:在系统级的应用中被占用)
部分信号的说明:
SIGINT:ctrl+c 终止信号
SIGQUIT:ctrl+\ 终止信号
SIGTSTP:ctrl+z 暂停信号
SIGALRM:闹钟信号 收到此信号后定时结束,结束进程
SIGCHLD:子进程状态改变,父进程收到信号
SIGKILL:杀死信号
信号处理的三种方式:忽略,捕捉和默认动作
- 忽略:就跟字面意思一样忽略掉它(注意:SIGKILL,SIGSTOP不能被忽略)
- 捕捉:就是一些信号处理的函数,然后让这个函数告诉内核,当信号产生时,内核调用该函数,实现某种信号的处理
- 默认动作:每个信号都有其对应的默认的处理动作,当触发了某种信号,系统就会立刻去执行。
信号的使用
其实对于常用的 kill 命令就是一个发送信号的工具,kill -9 PID或者使用命令kill -SIGKILL PID (二者作用一样)来杀死进程。比如,我在后台运行了一个 top 工具,通过 ps 命令可以查看他的 PID,通过 kill -9 来发送了一个终止进程的信号来结束了 top 进程。如果查看信号编号和名称,可以发现9对应的是 9) SIGKILL,正是杀死该进程的信号。而以下的执行过程实际也就是执行了9号信号的默认动作——杀死进程。
对于信号来说,最大的意义不是为了杀死信号,而是实现一些异步通讯(即信号处理方式第二种–捕捉)的手段
信号编程(入门)
信号处理函数的注册(信号绑定):signal (入门),sigaction(高级,可携带信号)
信号处理发送函数: kill (入门), sigqueue(高级,可携带信号)
信号处理函数的注册和信号处理发送函数-入门版
注册
#include <signal.h>typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
函数作用:信号绑定函数
signum:指明要绑定的信号类型,SIGKILL和SIGSTOP两个信号外的任何信号都可以
handler:指向绑定的函数指针,设置为SIG_IGN时代表忽略信号
返回值:成功返回信号处理程序的上一个值,出错返回SIG_ERR。
信号发送
int kill(pid_t pid,int sig);
函数作用:向指定进程发送指定信号pid:进程ID
sig:指定发送的信号
返回值:成功返回0,失败返回-1
signal_demo.c
注册信号函数,实现Ctrl+c无法中止进程
#include <signal.h>
#include <stdio.h>
void handler(int sigNum)
{
printf("sigNum is %d \n",sigNum);
printf("never quit\n");
}
int main()
{
signal(SIGINT,handler);
while(1);
return 0;
}
kill_demo.c
发送信号函数,触发信号,两种方式(KILL和system)
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <stdlib.h>
//typedef void (*sighandler_t)(int);
//sighandler_t signal(int signum, sighandler_t handler);
int main(int argc,char **argv)
{
int signum;
int pid;
char cmd[128]={0};
signum=atoi(argv[1]);//将字符串str转换成一个整数并返回结果
pid=atoi(argv[2]);
printf("signum=%d pid=%d\n",signum,pid);
// int kill(pid_t pid, int sig);
//方法一:
//kill(pid,signum); 发送信号x
//方法二 system调用脚本
sprintf(cmd,"kill -%d %d",signum,pid);
system(cmd);
printf("发送指令成功\n");
return 0;
}
信号的忽略
signal(SIGINT,SIG_IGN)//可以忽略掉ctrl c 的信号
信号携带消息(高级)
发信号:
1.用什么发 sigqueue()
2.怎么发消息
#include <signal.h>
//发给谁 发什么信号(比如上面讲的信号编号9 10等)
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval { //发送的消息(接收端也是用的这个联合体)
int sival_int;//发送整型
void *sival_ptr;//发送字符串
};
收信号:
1.用什么绑定函数(以及收到信号如何处理动作)sigaction()
2.如何读出消息
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
//sigactio(),会依照参数signum指定的信号编号来设置该信号的处理函数
//const struct sigaction *act,你要做什么
//struct sigaction *oldact,是否备份,不备份用NULL
struct sigaction { 凡是带有_t说明是个结构体
void (*sa_handler)(int); //信号处理程序,不接受额外数据,和signal()的参数handler一样了 SIG_IGN 为忽略,SIG_DFL 为默认动作
void (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用
sigset_t sa_mask;//阻塞关键字的信号集(默认阻塞),可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。
int sa_flags;//配置为SA_SIGINFO这个宏表示能够接受数据
};
//回调函数句柄sa_handler、sa_sigaction只能任选其一
siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
int si_trapno; /* Trap number that caused
hardware-generated signal
(unused on most architectures) */
pid_t si_pid; /* Sending process ID */谁发的
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
sigval_t si_value; /* Signal value */接收的数据 是个联合体 信号发送函数使用这个联合体
int si_int; /* POSIX.1b signal */数据
void *si_ptr; /* POSIX.1b signal */
int si_overrun; /* Timer overrun count; POSIX.1b timers */
int si_timerid; /* Timer ID; POSIX.1b timers */
void *si_addr; /* Memory location which caused fault */
int si_band; /* Band event */
int si_fd; /* File descriptor */
}
信号携带消息编程实现
收信号:
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
/*struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};*/
void handler(int signum, siginfo_t *info, void *context)//info里面的参数可以通过man手册查询
{
printf("signum=%d\n",signum);
if(context != NULL){
printf("get data=%d\n",info->si_int);
printf("get data=%d\n",info->si_value.sival_int);//和上面一样换了种方式
printf("发送者的pid=%d\n",info->si_pid);
}
}
int main()
{
struct sigaction act;
printf("pid=%d\n",getpid());
act.sa_sigaction=handler;
act.sa_flags=SA_SIGINFO;//能够获取到信息信息
//int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
sigaction(SIGUSR1,&act,NULL);//第三个是参数是备份的这边写NULL SIGUSR1对应的编号为10!!!
printf("22");
while(1);
return 0;
}
发信号:
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc,char **argv)
{
int signum;
int pid;
signum=atoi(argv[1]);//atoi把字符串转化为整形
pid=atoi(argv[2]);//接收进程的pid号
union sigval value;
value.sival_int=100;//发送一个整型数100的消息
/*union sigval {
int sival_int;
void *sival_ptr; 发送字符的话把地址传进来
};*/
//int sigqueue(pid_t pid, int sig, const union sigval value);
sigqueue(pid,signum,value);
printf("我的pid是:%d\n",getpid());
printf("发送完毕\n");
return 0;
}
信号量(Semaphore)
信号量:
信号量(Semaphore是一个计数器,用于实现进程间的互斥与同步,不用于存储进程间的通信数据
特点:
(1).用于进程间同步,若要在进程间传递数据需要结合共享内存
(2).信号量是基于PV操作,程序对信号量是原子操作
所谓原子操作,就是“不可中断的一个或一系列操作”,也就是不会被线程调度机制打断的操作
P、V操作(类似信号量lock、unlock)
可以这样理解:一间房间(临界资源)的门前有一个盒子,盒子里有钥匙(信号量),一个人拿了钥匙(P操作),开了门并走进了房间,且门外还有人等着,得等进去的人出来放钥匙(V操作),这个人才能拿钥匙(P操作)进入房间
多道程序系统中存在许多进程,它们共享各种资源,然而有很多资源一次只能供一个进程使用。一次仅允许一个进程使用的资源称为临界资源。许多物理设备都属于临界资源,如输入机、打印机、磁带机等。
相关api
最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。
Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。
API:
#include <sys/sem.h>
int semget(key_t key,int num_sems,int sem_flags);
//函数作用:创建或获取一个信号量组:
//key:由ftok生成的key标识,标识系统的唯一IPC资源。
//num_sems:需要创建的信号量数量
//sem_flags:如果要创建新的共享内存,需要使用IPC_CREAT,可配选项0666代表可读可写
//返回值:若成功返回信号量集ID,失败返回-1;
int semop(int semid,struct sembuf semoparray[],size_t numops);//函数作用:对信号量组进行操作,改变信号量的值
//semid:信号量ID,由semget()函数获得
//semoparray[]:信号量操作数组
//numops:进行操作信号量的个数,即sops结构变量的个数,需大于或等于1。最常见设置此值等于1,只完成对一个信号量的操作
//返回值:成功返回0,失败返回-1
int semctl(int semid,int sem_num,int cmd,union semun arg);//函数作用:控制信号量的相关信息
//semid:信号量ID,由semget()函数获得
//sem_num:第几个信号量,默认设为0,当使用到信号量集才设置其他值
//cmd:设置为SETVAL代表把信号量设置为一个已知的值
//arg:semun 联合体,存放信号量数值
//返回值:失败返回-1
union semun {
int val;//就把这个val值设置一下就好了,其他的不用管,默认就好
struct semid_ds *buf;//IPC_STAT、IPC_SET用的semid_ds结构
unsigned short *array;//SETALL、GETALL用的数组值
struct seminfo *_buf;
//为控制IPC_INFO提供的缓存
};
struct sembuf {
short semnum;/*信号量集合中的信号量编号,0代表第1个信号量*/
short val;//若val>0进行V操作信号量值加val,表示进程释放控制的资源
//若val<0进行P操作信号量值减val,则调用进程阻塞,直到资源可用//若设置IPC_NOWAIT不会睡眠,进程直接返回EAGAIN错误
//若val==0时阻塞等待信号量为0,调用进程进入睡眠状态,直到信号值为0;//若设置IPC_NOWAIT,进程不会睡眠,直接返回EAGAIN错误
short flag;//0 设置信号量的默认操作
//IPC_NOWAIT设置信号量操作不等待
//SEM_UNDO 选项会让内核记录一个与调用进程相关的UNDO记录,如果该进程崩溃,则根据这个进程的UNDO记录自动恢复相应信号量的计数值};
sem.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
//int semget(key_t key, int nsems, int semflg);
//int semctl(int semid, int semnum, int cmd, ...);
//int semop(int semid, struct sembuf *sops, unsigned nsops);
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
void pGetKey(int id)
{
struct sembuf set;
set.sem_num = 0;
set.sem_op = -1;
set.sem_flg = SEM_UNDO;
semop(id,&set,1);
printf("get key\n");
}
void vPutKey(int id)
{
struct sembuf set;
set.sem_num = 0;
set.sem_op = 1;
set.sem_flg = SEM_UNDO;
semop(id,&set,1);
printf("put key\n");
}
int main(int argc, char const *argv[])
{
key_t key;
int semid;
int pid;
union semun seminit;
seminit.val = 0;
key = ftok(".",1);
semid = semget(key,1,IPC_CREAT|0666);
semctl(semid,0,SETVAL,seminit);
pid = fork();
if(pid > 0){
pGetKey(semid);
printf("this is father\n");
vPutKey(semid);
}else if (pid == 0){
printf("this is child\n");
vPutKey(semid);
}else{
printf("fork error\n");
}
return 0;
}
信号量与共享内存配合实例
不加信号量实例
shmsend.c
#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdlib.h>
int main()
{
key_t key_shm;
int shmid;
char *send;
int i,cnt = 0;
if((key_shm = ftok(".",1)) == -1){
perror("ftok");
exit(-1);
}
printf("ftok successed! key is 0x%x\n",key_shm);
if((shmid =shmget(key_shm,4*1024,IPC_CREAT|0666)) == -1){
perror("shmget");
exit(-2);
}else{
printf("shmget successed! shmid is %d\n",shmid);
}
if(*(send=shmat(shmid,0,0)) == -1){
perror("shmat");
exit(-3);
}else{
printf("shmat successed!\n");
}
while(1){
for(i=0;i<10;i++){
*send = i+48;
send++;
memset(send,'\0',4*1024);
sleep(1);
}
send = send -i;
printf("cnt is %d\n",++cnt);;
if(cnt ==3){
break;
}
}
if(shmdt(send) == -1){
perror("shmdt");
exit(-4);
}else{
printf("shmdt successed\n");
}
if(shmctl(shmid,IPC_RMID,NULL) == -1){
perror("shmctl");
exit(-5);
}else{
printf("shmctl successed!\n");
}
return 0;
}
shmreceive.c
#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdlib.h>
int main()
{
key_t key_shm;
int shmid;
char *rcv;
if((key_shm = ftok(".",1)) == -1){
perror("ftok");
exit(-1);
}
printf("ftok successed!key is %x\n",key_shm);
if((shmid =shmget(key_shm,4*1024,IPC_CREAT|0666)) == -1){
perror("shmget");
exit(-2);
}else{
printf("shmget successed! shmid is %d\n",shmid);
}
if(*(rcv=shmat(shmid,0,0)) == -1){
perror("shmat");
exit(-3);
}else{
printf("shmat successed!\n");
}
while(1){
sleep(1);
printf("%s\n",rcv);
}
if(shmdt(rcv) == -1){
perror("shmdt");
exit(-4);
}else{
printf("shmdt successed\n");
}
if(shmctl(shmid,IPC_RMID,NULL) == -1){
perror("shmctl");
exit(-5);
}else{
printf("shmctl successed!\n");
}
return 0;
}
此时终端上的体现如下:
共享内存加信号量的实例
shm_sem_
目录
send.c
#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/sem.h>
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
void pGetKey(int id)
{
struct sembuf set;
set.sem_num = 0;
set.sem_op = -1;
set.sem_flg = SEM_UNDO;
semop(id,&set,1);
printf("get key\n");
}
void vPutKey(int id)
{
struct sembuf set;
set.sem_num = 0;
set.sem_op = 1;
set.sem_flg = SEM_UNDO;
semop(id,&set,1);
printf("put key\n");
}
int main()
{
key_t key_shm;
key_t key_sem;
int shmid;
int semid;
char *send;
int i,cnt = 0;
union semun seminit;
seminit.val = 1;
if((key_shm = ftok(".",1)) == -1){
perror("ftok");
exit(-1);
}
printf("ftok successed! key is 0x%x\n",key_shm);
if((shmid =shmget(key_shm,4*1024,IPC_CREAT|0666)) == -1){
perror("shmget");
exit(-2);
}else{
printf("shmget successed! shmid is %d\n",shmid);
}
if(*(send=shmat(shmid,0,0)) == -1){
perror("shmat");
exit(-3);
}else{
printf("shmat successed!\n");
}
if((key_sem = ftok(".",2)) == -1){
perror("ftok");
exit(-7);
}
printf("sem ftok successed! key is 0x%x\n",key_sem);
if((semid =semget(key_sem,1,IPC_CREAT|0666) == -1)){
perror("semget");
exit(-6);
}
if(semctl(semid,0,SETVAL,seminit) == -1){
perror("semctl");
exit(-8);
}else{
printf("semctl successed!\n");
}
while(1){
pGetKey(semid);
for(i=0;i<10;i++){
*send = i+48;
send++;
memset(send,'\0',4*1024);
sleep(1);
}
send = send -i;
vPutKey(semid);
printf("cnt is %d\n",++cnt);;
if(cnt ==3){
break;
}
}
if(shmdt(send) == -1){
perror("shmdt");
exit(-4);
}else{
printf("shmdt successed\n");
}
if(shmctl(shmid,IPC_RMID,NULL) == -1){
perror("shmctl");
exit(-5);
}else{
printf("shmctl successed!\n");
}
return 0;
}
shm_sem_receive.c
#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/sem.h>
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
void pGetKey(int id)
{
struct sembuf set;
set.sem_num = 0;
set.sem_op = -1;
set.sem_flg = SEM_UNDO;
semop(id,&set,1);
printf("get key\n");
}
void vPutKey(int id)
{
struct sembuf set;
set.sem_num = 0;
set.sem_op = 1;
set.sem_flg = SEM_UNDO;
semop(id,&set,1);
printf("put key\n");
}
int main()
{
key_t key_shm;
key_t key_sem;
int shmid;
int semid;
char *rcv;
if((key_shm = ftok(".",1)) == -1){
perror("ftok");
exit(-1);
}
printf("ftok successed!key is %x\n",key_shm);
if((shmid =shmget(key_shm,4*1024,IPC_CREAT|0666)) == -1){
perror("shmget");
exit(-2);
}else{
printf("shmget successed! shmid is %d\n",shmid);
}
if(*(rcv=shmat(shmid,0,0)) == -1){
perror("shmat");
exit(-3);
}else{
printf("shmat successed!\n");
}
if((key_sem = ftok(".",2)) == -1){
perror("ftok");
exit(-7);
}
printf("sem ftok successed! key is 0x%x\n",key_sem);
if((semid =semget(key_sem,1,IPC_CREAT|0666) == -1)){
perror("semget");
exit(-6);
}
while(1){
pGetKey(semid);
sleep(1);
printf("%s\n",rcv);
vPutKey(semid);
}
if(shmdt(rcv) == -1){
perror("shmdt");
exit(-4);
}else{
printf("shmdt successed\n");
}
if(shmctl(shmid,IPC_RMID,NULL) == -1){
perror("shmctl");
exit(-5);
}else{
printf("shmctl successed!\n");
}
return 0;
}