IO进程(进程间通信 IPC)

article/2025/7/1 20:50:31

 进程间通信方式IPC

1).早期的进程间通信:

无名管道(pipe),有名管道(fifo),信号(signal)

2).system V IPC:

共享内存(share memory),消息队列(message queue),信号等集(semaphore set)

3).BSD:

套接字(socket)

1.无名管道

三种通信模式:

单工(Simplex)

单工通信是指数据传输只能在一个方向上进行,通信双方中一方固定为发送端,另一方固定为接收端。这种模式无法实现双向交互。

典型应用场景包括广播、电视信号传输或键盘向计算机发送指令。发送端无法接收数据,接收端也无法发送数据。

双工(Duplex)

双工通信允许数据在双方之间同时双向传输,类似于电话通话。根据实现方式,分为以下两种:

全双工(Full-Duplex)

通信双方可以同时发送和接收数据,需要两条独立的物理通道或通过技术手段实现双向并发传输。例如网线中的双绞线或光纤通信。

半双工(Half-Duplex)

允许双向传输,但不能同时进行。同一时间只能有一方发送数据,典型例子是对讲机或传统集线器网络。需要协议控制传输方向切换。

特性单工半双工全双工
方向性单向固定双向交替双向同时
通道要求1条1条2条或复用技术
延迟无反向延迟存在切换延迟无延迟
典型协议HDMICAN总线TCP/IP

1.1 特点

(1)只能用于具有亲缘关系的进程之间才能通信

(2)半双工的通信模式,具有固定的读端fd[0]和写端fd[1]。

(3)管道可以看成是一种特殊的文件,对于它的读写可以使用文件IO如read、write函数。

(4)管道是基于文件描述符的通信方式。当一个管道建立时,它会创建两个文件描述符 fd[0]和fd[1]。其中fd[0]固定用于读管道,而fd[1]固定用于写管道。

(5)管道内没有数据的时候读阻塞

(6)管道内写满64k数据的时候写阻塞

(7)管道满后至少读出4k才能继续写操作

(8)关闭读端,写操作时会导致管道破裂

1.2 函数接口

int pipe(int fd[2])

头文件:#include <unistd.h>

功能:创建无名管道

参数:文件描述符 fd[0]:读端 fd[1]:写端

返回值:成功 0

              失败 -1 

1.3 练习

1.尝子进程向管道写数据,父进程从管道读数据:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>int main(int argc, const char *argv[])
{int fd[2] = {0};if(pipe(fd) < 0){perror("pipe err");return -1;}int pid = fork();                //两个进程进行pipe通信if(pid < 0){perror("fork err");exit(-1);}else if(pid == 0){waitpid(pid,NULL,0);close(fd[0]);                //关闭读端write(fd[1],"hello",5);      //向管道写入数据close(fd[1]);                //关闭写端exit(0);}else{close(fd[1]);                //关闭写端char buf[32] = "";read(fd[0],buf,32);          //读取数据printf("%s\n",buf);          close(fd[0]);                 //关闭读端wait(NULL);                  //等待子进程结束,并回收子进程资源exit(0);}//单个进程内对无名pipe管道内写入数据,读取数据/*write(fd[1],"hello",5);        //写入数据char buf[32] = "";read(fd[0],buf,32);            //读取数据printf("%s\n",buf);            printf("%d %d\n",fd[0],fd[1]);        //一般是3和4,因为0,1,2是输入,输出,错误标识符*/return 0;
}

为什么子进程我关了读端又往管道输入数据,管道没有破裂?因为这个时候我是关了子进程和管道的读端,但是我没有关父进程和管道的读端,还是有读端存在,所以不会破裂。

 

2.从父进程循环向管道输入数据,子进程循环从管道读取数据,直到输入quit,程序结束。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <sys/types.h>int main(int argc, const char *argv[])
{int fd[2] = {0};if(pipe(fd) < 0){perror("pipe err");return -1;}char buf[32] = "";                //定义在创建子进程前面,因为子进程会继承父进程在创建子进程前的资源int pid = fork();if(pid < 0){perror("fork err");exit(-1);}else if(pid == 0){                //子进程close(fd[1]);while(1){read(fd[0],buf,32);       //读取数据if(!strcmp(buf,"quit"))break;printf("%s\n",buf);}close(fd[0]);exit(0);}else{                            //父进程close(fd[0]);while(1){scanf("%s",buf);write(fd[1],buf,strlen(buf)+1);        //写入数据if(!strcmp(buf,"quit") )break;}close(fd[1]);wait(NULL);                //等待释放子进程资源exit(0);}return 0;
}

2.有名管道

2.1 特点

(1)有名管道可以使互不相关的两个进程互相通信。

(2)有名管道可以通过路径名来指出,并且在文件系统中可见,但内容存放在内存中。但是读写数据不会存在文件中,而是在管道中(也就是内核空间中)。

(3)进程通过文件IO来操作有名管道

(4)不支持如lseek() 操作

(5)有名管道遵循先进先出规则

2.2 函数接口

int mkfifo(const char *filename,mode_t mode);

头文件:#include <sys/types.h>
              #include <sys/stat.h>

功能:创健有名管道

参数:filename:有名管道文件名

mode:权限

返回值:成功:0

              失败:-1,并设置errno号

errno也需要头文件:#include <errno.h>

注意对错误的处理方式:

如果错误是file exist时,注意加判断,如:if(errno == EEXIST)

3.信号

kill -l: 显示系统中的信号

kill -num PID: 给某个进程发送信号

3.1概念:

在Linux系统中,信号是进程间通信的一种机制,用于通知进程发生了某种事件。C语言通过标准库和系统调用提供了信号处理的功能。

3.2特点

同步操作指的是任务按顺序执行,必须等待前一个任务完成后才能开始下一个任务。这种方式会阻塞当前线程,直到任务完成。

异步操作指的是任务可以同时进行,不需要等待前一个任务完成。这种方式不会阻塞当前线程,任务完成后通过回调、事件或通知机制处理结果。

(1)信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式(就是你的进程在运行,没关系,我可以直接和它通信,命令进程做一些动作)

(2)信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。

(3)如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。

3.3 信号的响应方式

(1)忽略信号:对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP。

(2)捕捉信号:定义信号处理函数,当信号发生时,执行相应的处理函数。

(3)执行缺省操作:Linux对每种信号都规定了默认操作(就比如当进程向一个读端已关闭的管道写入数据时,操作系统会向写入进程发送 SIGPIPE 信号,缺省行为是终止进程

3.4信号种类

常用:

SIGINT(2):中断信号,Ctrl-C 产生,用于中断进程

SIGQUIT(3):退出信号, Ctrl-\ 产生,用于退出进程并生成核心转储文件

SIGKILL(9):终止信号,用于强制终止进程。此信号不能被捕获或忽略。

SIGALRM(14):闹钟信号,当由 alarm() 函数设置的定时器超时时产生。

SIGTERM(15):终止信号,用于请求终止进程。此信号可以被捕获或忽略。termination

SIGCHLD(17):子进程状态改变信号,当子进程停止或终止时产生。

SIGCONT(18):继续执行信号,用于恢复先前停止的进程。

SIGSTOP(19):停止执行信号,用于强制停止进程。此信号不能被捕获或忽略。

SIGTSTP(20):键盘停止信号,通常由用户按下 Ctrl-Z 产生,用于请求停止进程。

3.5函数接口

3.4.1信号发送和挂起

特性阻塞挂起
触发原因等待资源或事件(如I/O完成)外部干预(如操作系统、用户)
控制方进程自身行为操作系统或用户请求
状态等待状态(Waiting)挂起状态(Suspended)
内存位置保持在内存中可能被换出到磁盘
恢复方式资源就绪或事件发生自动恢复需要显式恢复命令
持续时间通常较短可能很长
资源占用占用内存不占用或少量占用内存
典型场景管道读写等待、锁获取系统负载均衡、调试

int kill(pid_t pid, int sig);

头文件:#include <signal.h>

功能:信号发送

参数:pid:指定进程

           sig:要发送的信号

返回值:成功 0

              失败 -1

int raise(int sig);

头文件:#include <signal.h>

功能:进程向自己发送信号

参数:sig:信号

返回值:成功 0

              失败 -1

相当于:kill(getpid(),sig);

int pause(void);

头文件:#include <unistd.h>

功能:用于将调用的进程挂起,直到收到被捕获处理的信号为止。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
int main(int argc, const char *argv[])
{//kill(getpid(),SIGKILL);				//杀死当前进程//raise(SIGKILL);						//杀死当前进程pause();printf("ok\n");return 0;
}

3.4.2定时器

unsigned int alarm(unsigned int seconds)

头函数:#include <unistd.h>

功能:在进程中设置一个定时器。当定时器指定的时间到了时,它就向进程发送SIGALARM信号。

参数:seconds:定时时间,单位为秒

返回值:如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。

注意:一个进程只能有一个闹钟时间。如果在调用alarm时已设置过闹钟时间,则之前的闹钟时间被新值所代替。

常用操作:取消定时器alarm(0),返回旧闹钟余下秒数。

#include <stdio.h>
#include <unistd.h>int main(int argc, const char *argv[])
{printf("%d\n",alarm(9));        //设置9秒闹钟,返回值是零sleep(3);printf("%d\n",alarm(2));        //返回值是之前闹钟剩余时间,是9-3pause();return 0;
}

3.4.3信号处理函数

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

头文件:#include <signal.h>

功能:信号处理函数

参数:signum:要处理的信号

           handler:信号处理方式

                   SIG_IGN:忽略信号 (忽略 ignore)

                   SIG_DFL:执行默认操作 (默认 default)

                   handler:捕捉信号 (handler为函数名,可以自定义)

void handler(int sig){} //函数名可以自定义, 参数为要处理的信号

返回值:成功:设置之前的信号处理方式

              失败:-1

#include <stdio.h>
#include <signal.h>void handler(int sig){printf("\nctrl + c:%d\n",sig);
}
int main(int argc, const char *argv[])
{signal(SIGINT,handler);			//2signal(SIGQUIT,handler);		//3while(1);return 0;
}

 

根据这个练习我简单讲一下这个函数逻辑:这个函数传入的参数是一个Int类型数据和一个函数指针,我们自定义一个函数handler(),传入参数就是void (*sighandler_t)(int) = handler,这样就可以把函数传入另一个函数,然后在另一个函数调用它,你就可以在handler函数内写上你想让进程接收到某个信号signum后进行你指定的操作,handler函数传入的参数也是固定的,能识别你前面的信号signum,在handler内可以使用这个信号值,就如代码中我们成功打印了这个信号的num。(把函数当做参数传入,然后函数写上你接收到信号后想要进程进行的操作

练习

用信号的知识实现司机和售票员问题。

(1)售票员捕捉SIGINT(代表开车)信号,向司机发送SIGUSR1信号,司机打印(let's gogogo)

(2)售票员捕捉SIGQUIT(代表停车)信号,向司机发送SIGUSR2信号,司机打印(stop the bus)

(3)司机捕捉SIGTSTP(代表到达终点站)信号,向售票员发送SIGUSR1信号,售票员打印(please get off the bus)

(4)司机等待售票员下车,之后司机再下车。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/wait.h>
#include <stdlib.h>
int fatherpid,childpid;void driver(int sig){if(sig == SIGUSR1){			//识别到SIGUSR1信号后打印let's gogogoprintf("\nlet's gogogo\n");}else if(sig == SIGUSR2){	//识别到SIGUSR2信号后打印stop the busprintf("\nstop the bus\n");}else if(sig == SIGTSTP){	//识别到SIGTSTP信号后向子进程(售货员)发送SIGUSR1信号kill(childpid,SIGUSR1);}
}
void salar(int sig){if(sig == SIGINT){			//识别到SIGINT信号后向父进程(司机)发送SIGUSR1信号kill(fatherpid,SIGUSR1);}else if(sig == SIGQUIT){	//识别到SIGQUIT信号后向父进程发送SIGUSR2信号kill(fatherpid,SIGUSR2);}else if(sig == SIGUSR1){	//识别到SIGUSR1信号后打印please get off the bus,并且退出子进程printf("\nplease get off the bus\n");exit(0);}
}int main(int argc, const char *argv[])
{fatherpid = getpid();childpid = fork();if(childpid < 0){perror("fork err");exit(0);}else if(childpid == 0){			//子进程充当售货员signal(SIGINT,salar);		//售货员对信号SIGINT处理(ctrl c)signal(SIGQUIT,salar);		//售货员对信号SIGQUIT处理(ctrl \)signal(SIGUSR1,salar);		//售货员对信号SIGTSTP处理signal(SIGTSTP,SIG_IGN);	//售货员要忽略键盘停止信号(ctrl z)while(1);}else{							//父进程充当司机signal(SIGUSR1,driver);		//司机对信号SIGUSR1处理signal(SIGUSR2,driver);		//司机对信号SIGUSR2处理signal(SIGTSTP,driver);		//司机对信号SIGTSTP处理(ctrl z)signal(SIGINT,SIG_IGN);		//司机要忽略中断信号(ctrl c)signal(SIGQUIT,SIG_IGN);	//司机要忽略退出信号(ctrl \)wait(NULL);					//等待子进程结束(售货员不下车司机不下车)}return 0;
}

 

4.共享内存

4.1 概念&特点:

(1)共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝。

(2)为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程

将其映射到自己的私有地址空间。进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。

(3)由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等

 用户级页表定义

用户级页表是每个用户进程独有的数据结构,用于将进程的虚拟地址空间映射到物理内存地址。它由操作系统创建和管理,但对用户进程透明。

4.2 实现步骤

(1)创建key值

(2)创建或打开共享内存

(3)映射共享内存到用户空间

(4)撤销映射

(5)删除映射

4.3 函数接口

key_t ftok(const char *pathname, int proj_id);

头文件:#include <sys/ipc.h>

               #include <sys/types.h>

功能:创建出来的具有唯一映射关系的一个key值,帮助操作系统用来标识一块共享内存

参数:Pathname:已经存在的可访问文件的名字

           Proj_id:一个字符(因为只用低8位)

返回值:成功:key值

              失败:-1

int shmget(key_t key, size_t size, int shmflg);

头文件:#include <sys/ipc.h>

               #include <sys/shm.h>

功能:创建或打开共享内存

参数:key 键值

           size 共享内存的大小

           shmflg IPC_CREAT|IPC_EXCL|0777

作用行为

 IPC_CREAT

 如果 IPC 对象不存在,则创建它

1.当对象不存在时:创建新对象

2.当对象已存在时:返回现有对象的标识符

IPC_EXCL

与 IPC_CREAT 配合使用,确保创建新对象

1.当对象不存在时:创建新对象

2.当对象已存在时:失败,返回错误 

0777(权限模式)设置 IPC 对象的访问权限

0 : 特殊前缀(八进制)

7 (rwx): 所有者权限

7 (rwx): 组权限

7 (rwx): 其他用户权限

返回值:成功 shmid

              出错 -1

注意对错误的处理方式:

如果错误是file exist光打开共享内存不用设IPC_CREAT|IPC_EXCL了,加判断,如:if(errno == EEXIST)

void *shmat(int shmid,const void *shmaddr,int shmflg); //attaches

头文件:#include <sys/types.h>

               #include <sys/shm.h>

功能:映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问

参数:shmid 共享内存的id号

           shmaddr 一般为NULL,表示由系统自动完成映射

           如果不为NULL,那么有用户指定

           shmflg:SHM_RDONLY就是对该共享内存只进行读操作

           0 :可读可写

返回值:成功:完成映射后的地址,

              出错:-1(地址)

用法:if((p = (char *)shmat(shmid,NULL,0)) == (char *)-1)

int shmdt(const void *shmaddr);                         //detaches

头文件:#include <sys/types.h>

               #include <sys/shm.h>

功能:取消映射

参数:要取消的地址

返回值:成功0

               失败的-1

int shmctl(int shmid,int cmd,struct shmid_ds *buf);         //control

头文件:#include <sys/ipc.h>

               #include <sys/shm.h>

功能:(删除共享内存),对共享内存进行各种操作

参数:shmid 共享内存的id号

           cmd IPC_STAT 获得shmid属性信息,存放在第三参数

           IPC_SET 设置shmid属性信息,要设置的属性放在第三参数

           IPC_RMID:删除共享内存,此时第三个参数为NULL即可

           buf shmid所指向的共享内存的地址,空间被释放以后地址就赋值为null

返回:成功0

           失败-1

用法:shmctl(shmid,IPC_RMID,NULL);

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <sys/shm.h>
#include <errno.h>int main(int argc, const char *argv[])
{//创建唯一的映射关系的key值key_t key;key = ftok("2",'a');if(key < 0){perror("ftok err");return -1;}printf("sucess!\n");//创建或者打开共享内存int shmid = shmget(key,128,IPC_CREAT|IPC_EXCL|0777);	//创建共享内存if(shmid < 0){//当共享内存存在的时候会报错-1if(errno == EEXIST){		shmid = shmget(key,128,0777);	//打开共享内存}else{perror("shmid err");return -1;}}printf("%d\n",shmid);					//打印//映射共享内存到进程所在的地址空间int *p = (int *)shmat(shmid,NULL,0);		//这个指针的类型是int,当然你也可以让他是别的类型(char float等)if(p == (int *) - 1){perror("shmat err");return -1;}scanf("%d",p);printf("%d\n",*p);//取消映射shmdt(p);//删除映射shmctl(shmid,IPC_RMID,NULL);return 0;
}

4.4 相关命令:

ipcs -m: 查看系统中的共享内存

ipcrm -m shmid:删除共享内存

ps: 可能不能直接删除掉还存在进程使用的共享内存。

这时候可以用ps -ef对进程进行查看,kill掉多余的进程后,再使用ipcs查看。

4.5 练习

两个进程实现通信,一个进程循环从终端输入,另一个进程循环打印,当输入quit时结束

input.c:

#include <stdio.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ipc.h>
#include <string.h>
typedef struct share{        //使用结构体,如果不使用结构体就无法让输出进程知道输入进程输入的数据是最新的还是原来的,输出进程就会一直循环输出int flag;                //是否输出的标志,1的时候代表共享内存的数据是新输入的,就不再输入,需要等待输出;0的时候代表共享内存中的数据是输出过的,需要再输入char msg[64];
}*share;
int main(int argc, const char *argv[])
{key_t key = ftok("2",'a');if(key < 0){perror("ftok err");return -1;}int shmid = shmget(key,128,IPC_CREAT | IPC_EXCL | 0777);if(shmid < 0){if(errno == EEXIST)shmid = shmget(key,128,0777);else{perror("shmget err");return -1;}}printf("shmid:%d\n",shmid);share p = (share)shmat(shmid,NULL,0);if(p == (share)-1){perror("shmat err");return -1;}p->flag = 0;                //初始化标志while(1){        if(p->flag == 0){        //当标志为0,输入数据fgets(p->msg,sizeof(p->msg),stdin);if(p->msg[strlen(p->msg) - 1] == '\n')p->msg[strlen(p->msg) - 1] = '\0';//scanf("%s",p->msg);        //scanf有缺陷,不能输入空格,只能输入一个字符串,如果输入两个字符串,会分开输入,输出进程就会分两行输出这两个字符串p->flag = 1;if(strcmp(p->msg,"quit") == 0)break;}}
//结束映射shmdt(p);shmctl(shmid,IPC_RMID,NULL);return 0;
}

output.c:

#include <stdio.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ipc.h>
#include <string.h>
typedef struct share{            //和输入函数一样,不然数据不一致无法识别共享内存中的数据int flag;char msg[64];
}*share;
int main(int argc, const char *argv[])
{key_t key = ftok("2",'a');if(key < 0){perror("ftok err");return -1;}int shmid = shmget(key,128,IPC_CREAT | IPC_EXCL | 0777);if(shmid < 0){if(errno == EEXIST)shmid = shmget(key,128,0777);else{perror("shmget err");return -1;}}printf("shmid:%d\n",shmid);share p = (share)shmat(shmid,NULL,0);if(p == (share)-1){perror("shmat err");return -1;}p->flag = 0;                //初始化标志while(1){if(p->flag ==1){            //当标志为1的时候输出if(strcmp(p->msg,"quit") == 0)break;printf("%s\n",p->msg);p->flag = 0;}}shmdt(p);shmctl(shmid,IPC_RMID,NULL);return 0;
}

 5.信号灯集

5.1 特点

(1)信号灯(semaphore),也叫信号量,信号灯集是一个信号灯的集合。它是不同进程间或一个给定进程内部不同线程间同步的机制;

(2)而Posix信号灯指的是单个计数信号灯:无名信号灯、有名信号灯。(主要学的是无名信号灯)

(3)System V的信号灯是一个或者多个信号灯的一个集合。其中的每一个都是单独的计数信号灯。

通过信号灯集实现共享内存的同步操作

5.2 步骤

(1).创建key值: ftok

(2).创建或打开信号灯集: semget

(3).初始化信号灯: semctl

(4).PV操作: semop

(5).删除信号灯集: semctl

5.3 相关命令

ipcs -s: 查看信号灯集

ipcrm -s: 删除信号灯集

注意:有时候可能会创建失败,或者semid为0,所以可以用命令查看,删除了重新创建就可以。

5.4 函数接口

int semget(key_t key, int nsems, int semflg);
功能:创建/打开信号灯
参数:key:ftok产生的key值nsems:信号灯集中包含的信号灯数目semflg:信号灯集的访问权限,通常为IPC_CREAT|IPC_EXCL|0666
返回值:成功:信号灯集ID失败:-1int semctl ( int semid, int semnum,  int cmd…/*union semun arg*/);
功能:信号灯集合的控制(初始化/删除)
参数:semid:信号灯集IDsemnum: 要操作的集合中的信号灯编号,信号灯编号从0开始cmd: GETVAL:获取信号灯的值,返回值是获得值SETVAL:设置信号灯的值,需要用到第四个参数:共用体IPC_RMID:从系统中删除信号灯集合
返回值:成功 0失败 -1
用法:
1. 初始化信号灯集:
需要自定义共用体
union semun{int val;
} mysemun;
mysemun.val = 10;
semctl(semid, 0, SETVAL, mysemun);2. 获取信号灯值:函数semctl(semid, 0, GETVAL)的返回值
3. 删除信号灯集:semctl(semid, 0, IPC_RMID);int semop ( int semid, struct sembuf  *opsptr,  size_t  nops);
功能:对信号灯集合中的信号量进行PV操作
参数:semid:信号灯集IDopsptr:操作方式nops:  要操作的信号灯的个数 1个
返回值:成功 :0失败:-1
struct sembuf {short  sem_num; // 要操作的信号灯的编号short  sem_op;  //    0 :  等待,直到信号灯的值变成0//   1  :  释放资源,V操作//   -1 :  申请资源,P操作                    short  sem_flg; // 0(阻塞),IPC_NOWAIT, SEM_UNDO
};用法:
申请资源 P操作:mysembuf.sem_num = 0;mysembuf.sem_op = -1;mysembuf.sem_flg = 0;semop(semid, &mysembuf, 1);
释放资源 V操作:mysembuf.sem_num = 0;mysembuf.sem_op = 1;mysembuf.sem_flg = 0;semop(semid, &mysembuf, 1);

6.消息队列

6.1特点

(1)消息队列是IPC对象(活动在内核级别的一种进程间通信的工具)的一种

(2)一个消息队列由一个标识符 (即队列ID)来标识

(3)消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等

(4)消息队列可以按照类型(自己设一个值作为类型)来发送/接收消息

6.2步骤

(1)创建key值: ftok()

(2)创建或打开消息队列: msgget()

(3)添加消息: 按照类型把消息添加到已经打开的消息队列末尾msgsnd()

(4)读取消息:可以按照类型把消息从列表中取走 msgrcv()

(5)删除消息队列msgctl()

6.3相关命令

ipcs -q:查看消息队列

ipcrm -q msgid:删除消息队列

6.4函数接口

int msgget(key_t key, int flag);

头文件:#include <sys/types.h>

              #include <sys/ipc.h>

              #include <sys/msg.h>

功能:创建或打开一个消息队列

参数: key值

            flag:创建消息队列的权限IPC_CREAT|IPC_EXCL|0666

返回值:成功:msgid

              失败:-1

int msgsnd(int msqid, const void *msgp, size_t size, int flag);

头文件:#include <sys/types.h>

              #include <sys/ipc.h>

              #include <sys/msg.h>

功能:添加消息

参数:msqid:消息队列的ID

           msgp:指向消息的指针。常用消息结构msgbuf如下:

                                struct msgbuf{        

                                        long mtype;         //消息类型,类型是大于0的

                                        char mtext[N]}; //消息正文

           size:发送的消息正文的字节数

           flag:IPC_NOWAIT:消息没有发送完成函数也会立即返回

                     0:直到发送完成函数才返回

返回值:成功:0

              失败:-1

使用:msgsnd(msgid, &msg,sizeof(msg)-sizeof(long), 0)

注意:消息结构除了第一个成员必须为long类型外,其他成员可以根据应用的需求自行定义。

int msgrcv(int msgid, void* msgp, size_t size, long msgtype, int flag);

头文件:#include <sys/types.h>

              #include <sys/ipc.h>

              #include <sys/msg.h>

功能:读取消息

参数:msgid:消息队列的ID

           msgp:存放读取消息的空间

           size:接受的消息正文的字节数(sizeof(msgp)-sizeof(long))

           msgtype:0:接收消息队列中第一个消息。

                            大于0:接收消息队列中第一个类型为msgtyp的消息.

                            小于0:接收消息队列中类型值不小于msgtyp的绝对值且类型值又最小的消息。

           flag:0:若无消息函数会一直阻塞

                     IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG

返回值:成功:接收到的消息的长度

               失败:-1

int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );

头文件:#include <sys/types.h>

              #include <sys/ipc.h>

              #include <sys/msg.h>

功能:对消息队列的操作,删除消息队列

参数:msqid:消息队列的队列ID

           cmd:IPC_STAT:读取消息队列的属性,并将其保存在buf指向的缓冲区中。

                      IPC_SET:设置消息队列的属性。这个值取自buf参数。

                      IPC_RMID:从系统中删除消息队列。

           buf:消息队列缓冲区

返回值:成功:0

               失败:-1

用法:msgctl(msgid, IPC_RMID, NULL)

 例:

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>
#include <errno.h>typedef struct msgbuf{                    //自定义一个消息结构体long type;int num;char ch;
}msgbuf;int main(int argc, const char *argv[])
{key_t key = ftok("2",'c');            //创建keyif(key < 0){perror("ftok err");return -1;}int msgid = msgget(key,IPC_CREAT | IPC_EXCL | 0777);        //使用key创建消息队列if(msgid < 0){if(errno = EEXIST)msgid = msgget(key,0777);else{perror("msgget err");return -1;}}printf("msgid:%d\n",msgid);//创建消息队列msgbuf msg;msg.type = 1;msg.num = 2;msg.ch = 'b';//向队列发送消息msgsnd(msgid,&msg,sizeof(msg) - sizeof(msg.type),0);msgbuf msg1;//从队列中接收消息msgrcv(msgid,&msg1,sizeof(msg1) - sizeof(msg.type),1,0);printf("%d %c\n",msg1.num,msg1.ch);return 0;
}

6.5练习: 

一个进程循环向另一个进程发送消息,直到输入的num为-1。

msg1.c(发送端):

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>typedef struct msgbuf{long type;int num;char ch;
}msgbuf;int main(int argc, const char *argv[])
{key_t key;key = ftok("2",'d');if(key < 0){perror("ftok err");return -1;}int msgid = msgget(key,IPC_CREAT | IPC_EXCL | 0777);if(msgid < 0){if(errno == EEXIST)msgid = msgget(key,0777);else{perror("msgget err");return -1;}}printf("msgid:%d\n",msgid);msgbuf msg1;while(1){scanf("%ld",&msg1.type);scanf("%d",&msg1.num);getchar();scanf("%c",&msg1.ch);msgsnd(msgid,&msg1,sizeof(msg1) - sizeof(msg1.type),0);if(msg1.num == -1)break;}msgctl(msgid,IPC_RMID,NULL);return 0;
}

 msg2.c(接受端):

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>typedef struct msgbuf{long type;int num;char ch;
}msgbuf;int main(int argc, const char *argv[])
{key_t key;key = ftok("2",'d');if(key < 0){perror("ftok err");return -1;}int msgid = msgget(key,IPC_CREAT | IPC_EXCL | 0777);if(msgid < 0){if(errno == EEXIST)msgid = msgget(key,0777);else{perror("msgget err");return -1;}}printf("msgid:%d\n",msgid);while(1){msgbuf msg2;msgrcv(msgid,&msg2,sizeof(msg2) - sizeof(msg2.type),0,0);if(msg2.num == -1)break;printf("type:%ld num:%d ch:%c\n",msg2.type,msg2.num,msg2.ch);}msgctl(msgid,IPC_RMID,NULL);return 0;
}

 


http://www.hkcw.cn/article/ssLWiPiDwB.shtml

相关文章

端午时节,粽香四溢

端午时节&#xff0c;粽香四溢&#xff0c;那缕缕清香仿佛在召唤着我们共赴这一夏的热情之约。在这充满活力与希望的氛围里&#xff0c;codigger 作为分布式操作系统闪耀登场。它凭借高效协同的工作模式&#xff0c;让各个节点紧密配合、无缝衔接&#xff1b;以灵活拓展的架构&…

贪心算法实战3

文章目录 前言区间问题跳跃游戏跳跃游戏II用最少数量的箭引爆气球无重叠区间划分字母区间合并区间 最大子序和加油站监控二叉树 前言 今天继续带大家进行贪心算法的实战篇3&#xff0c;本章注意来解答一些运用贪心算法的比较难的问题&#xff0c;大家好好体会&#xff0c;怎么…

建筑水电燃气能耗管理系统

系统介绍 能耗管理系统使用物联网技术采集分布各地的电表、水表、燃气表等能源计量仪表数据&#xff0c;同时使用大数据技术对数据进行处理与存储。为满足用户对能源消耗的精细化管理&#xff0c;平台提供能耗看板、支路能耗统计、能耗同比/环比比较、能源流向图、能耗折标、单…

美国关税演了一出律政戏 特朗普政策遭法院叫停

最近国际贸易圈发生了一系列戏剧性的事件,特朗普的关税政策成为焦点。2025年4月2日,特朗普签署了一项“对等关税”行政令,宣布这一天为“解放日”。根据这项行政令,美国将对中国征收30%的关税,对墨西哥和加拿大的部分商品征收25%的关税,并对大多数进口商品征收10%的普遍关…

委内瑞拉宣布加入国际调解院 促进多极化与和平公正

5月30日,委内瑞拉外交部宣布正式加入国际调解院。公告中提到,由中国牵头成立的国际调解院是一个强有力的多边工具,其成立标志着国际秩序向多极化、多中心、和平公正方向发展的决定性转变。委内瑞拉祝贺并感谢中国政府为世界和平、国际正义及全球南方国家发展作出的历史性贡献…

Error: Flash Download failed - Could not load file “xxx.axf“

今天在Keil uVision5用ST-LINKV2仿真器下载程序到板卡上&#xff0c;报如下错误&#xff1a; 到上图提示的目录下&#xff0c;发现确实没有生成axf文件。解决方法如下&#xff1a; 按下图单击魔棒工具栏按钮&#xff1a; 在弹出的对话框中选择“C/C”&#xff0c;然后勾选“C9…

MES管理系统:Java+Vue,含源码与文档,实现生产过程实时监控、调度与优化,提升制造企业效能

前言&#xff1a; 在当今竞争激烈的制造业环境中&#xff0c;企业面临着提高生产效率、降低成本、提升产品质量以及快速响应市场变化等多重挑战。MES管理系统作为连接企业上层计划管理系统与底层工业控制之间的桥梁&#xff0c;扮演着至关重要的角色。它能够实时收集、分析和处…

IMU--编程

教程 IMU编程主要分两部分&#xff1a;下位机的控制器获取IMU数据发送给上位机&#xff0c;上位机获取IMU数据之后进行算法处理。 下位机编程 上位机编程 机器人中&#xff0c;上位机一般用ros实现&#xff0c;上位机接收到数据之后&#xff0c;需要进行哪些处理&#xff1f; …

袁弘张歆艺发文庆祝结婚九周年 幸福九年依旧甜蜜

5月30日晚,演员袁弘在社交平台发文庆祝结婚九周年,并祝愿天下有情人幸福长久。他还幽默地提到蛋糕上的四坨狗爪印,说狗狗太馋了。随后,张含韵转发并配文:这个蛋糕,九(周年)四(坨)甜。网友们纷纷留言祝福他们永远幸福下去。前一天,袁弘更新社交平台晒出张歆艺手捧鲜花…

微信朋友圈能折叠了 优化用户体验新举措

近日,有网友发现微信朋友圈新增了“折叠”功能。当好友发布多条信息时,这些信息可能会被折叠,在朋友圈信息下方显示“余下x条”,点击后可查看好友发布的其他消息。腾讯客服对此表示,如果用户频繁发送广告营销等内容,为了优化用户体验,这些信息会被折叠。用户点击该条朋友…

研究表明非洲或正一分为二 超级地幔柱驱动分裂

最新研究揭示,非洲大陆下方存在一个巨大的超级热岩柱,正在上升并引发剧烈火山活动,导致非洲大陆逐渐分裂成两部分。地质学家早就注意到东非裂谷系统区域的缓慢分裂现象,但其背后的驱动力一直存在争议。现在,一项新研究提供了地球化学证据,支持了理论预测中超级地幔柱的存…

腹肌有几块其实是天生的 腱划数量决定一切

腹肌是腹部肌群的统称,日常可以通过肉眼观察到的主要是腹外斜肌和腹直肌。此外,还有腹横肌和腹内斜肌。通常所说的“几块腹肌”指的是腹直肌。责任编辑:zx0001

原来龙舟是这样造出来的 匠心独运的工艺传承

端午佳节,龙舟竞渡的热闹场景总是令人心潮澎湃。一艘艘如箭般飞驰在水面上的龙舟,承载着中华民族千年的文化记忆,也凝聚了无数匠人的心血与智慧。一块普通的木料如何一步步变成乘风破浪的龙舟?今天就让我们走进龙舟制作的世界,探寻其中的奥秘。制作龙舟时,选材是至关重要…

37岁2胎爸爸患上产后抑郁 男性心理困境需关注

张华,一个37岁的男人,拿着诊断报告声音颤抖地向医生确认:“我竟然得了产后抑郁?”这个问题通常被认为是“妈妈专属”,现在却击垮了他的心理防线。白天上班、接送大孩子上学放学,晚上还要照顾小的,这样的生活节奏对于普通上班族来说确实很辛苦。张华的生活也是如此,他白…

拍下菲律宾下水道女子摄影师发声 揭示地下生活真相

5月26日下午5点30分左右,在菲律宾马卡蒂闹市街头的下水道排水孔处,突然钻出一名黑色长发女子。她身形消瘦,面对市民的镜头时露出诡异微笑。业余摄影师威廉罗伯茨拍下了这一幕并发布在网上,迅速引发关注。许多网友称这是现实版的“下水道美人鱼”,或是日本恐怖电影《贞子》…

平价雪糕回归市场主流 理性消费成趋势

五月下旬,夏日的热浪开始席卷济南的每个角落,雪糕经销商们迎来一年中最忙的时候。记者探访了济南市场的情况。在济南市天桥区世茂天城附近的一家雪糕批发店里,店主姬先生正在忙着给冰柜上货。时值下班高峰期,店里顾客不少。姬先生经营雪糕批发生意已有八年,积累了大量回头…

律师称故意毁损文物罪最高可判10年 跳坑推倒兵马俑或将严惩

据报道,一名男子主动跳入兵马俑坑,推倒了兵马俑后躺在地上捂脸,随后场馆暂时封闭。兵马俑三号坑游览区域为高台结构,距坑内有约3米的落差,并且在游览区域与俑坑交界处设有围栏等保护措施。对于跳入兵马俑坑并推倒兵马俑的行为,知名律师河南泽槿律师事务所主任付建表示,兵…

烧烤摊主因投喂流浪狗营业额猛增 爱心与生意双赢

清晨6点,天刚蒙蒙亮,常州一家烤鸭店门口已经蹲着几只毛茸茸的“老顾客”。它们不吵不闹,只是眼巴巴地望着店里忙碌的身影——37岁的许老板正麻利地剁着烤鸭,顺手把切下的鸭屁股装进小碗,轻轻放在店门口。“来,吃吧!”他笑着招呼道。一只穿着小花袄的泰迪熟练地凑过来,尾…

直击特朗普记者会 马斯克谈眼角淤青 告别白宫获赠金钥匙

5月30日,马斯克带着眼角的淤青与特朗普一同出席了在白宫举行的告别新闻发布会。面对记者关于他眼睛伤势的询问,马斯克幽默地回应称:“我可没去法国啊。” 他解释说,这是他在和5岁的儿子小X玩耍时不小心被打到的。当天,特朗普送给马斯克一把金色的白宫钥匙。这标志着马斯克…

设计模式之结构型:装饰器模式

装饰器模式(Decorator Pattern) 定义 装饰器模式是一种​​结构型设计模式​​&#xff0c;允许​​动态地为对象添加新功能​​&#xff0c;而无需修改其原始类。它通过将对象包装在装饰器类中&#xff0c;以​​组合代替继承​​&#xff0c;实现功能的灵活扩展(如 Java I/O …