【Linux】进程间通信IPC

ops/2025/1/17 23:03:47/

目录

进程间通信 IPC

1. 进程间通信方式

2. 无名管道

2.1 特点

2.2 函数接口

2.3 注意事项

3. 有名管道

3.1 特点

3.2 函数接口

3.3 注意事项

3.4 有名管道和无名管道的区别

4. 信号

4.1概念

4.2信号的响应方式

4.3 信号种类

4.4 函数接口

4.4.1 信号发送和挂起

4.4.2 定时器

4.4.3 信号处理函数signal()

5. 共享内存

5.1特点

5.2步骤

5.3函数接口

5.4命令

6. 信号灯集

6.1 特点

6.2步骤

6.3 命令

6.4 函数接口

创建和使用信号灯:

函数操作:

把信号灯集加到共享内存:

7.消息队列:messagequeue

7.1特点

7.2步骤

7.3命令

7.4函数接口


进程间通信 IPC

InterProcess Communication

1. 进程间通信方式

1)早期的进程间通信:

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

2)systerm V IPC:

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

3)BSD:

套接字(socket)

2. 无名管道

2.1 特点

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

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

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

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

2.2 函数接口

int pipe(int fd[2])功能:创建无名管道
参数:文件描述符 fd[0]:读端  fd[1]:写端
返回值:成功 0失败 -1
#include <stdio.h>
#include <unistd.h>int main(int argc, char const *argv[])
{char buf[65536] = "";int fd[2] = {0}; //fd[0]代表读端,fd[1]代表写端if (pipe(fd) < 0){perror("pipe err");return -1;}printf("%d %d\n", fd[0], fd[1]);//读写// write(fd[1], "hello", 32);// read(fd[0], buf, 32);// printf("%s\n", buf);//结构类似队列,先进先出//1. 当管道中没有数据时,读阻塞。// read(fd[0], buf, 32);// printf("%s\n", buf);//但是关闭写端就不一样//当管道中有数据,关闭写端可以读出数据来。无数据,关闭写端,读操作会立即返回(返回0)。//write(fd[1],"hello",5);// close(fd[1]);// read(fd[0],buf,5);// printf("%s\n",buf);//2.当管道这个写满数据时,写阻塞,管道空间大小是64K// write(fd[1], buf, 65536);// printf("write full\n");// write(fd[1],"a",1);         //阻塞,因为管道中被写满了已经。// printf("write after\n");//3.写满一次后,当管道中至少有4K空间时(也就是读了4K),才可以继续写,否则阻塞。//先写满一次再读// read(fd[0], buf, 4096); //如果读4095后面写就阻塞了,因为不到4K空间。// write(fd[1], "k", 1);// printf("write after\n");//4.当读端关闭,向管道中写入数据无意义,管道破裂,进程会收到内核发送的SIGNAL信号close(fd[0]);write(fd[1], "hello", 5);printf("read close!\n");return 0;
}

2.3 注意事项

(1) 当管道中无数据的时候,读操作会阻塞。

管道当中有数据,关闭写段,可以将数据读出。

管道中无数据,关闭写段,读操作会立即返回。

(2) 管道中写满时(管道大小64K)写操作会阻塞,写满一次时一旦有4K空间可以继续写。

(3) 只有管道的读端存在时,向管道中写数据才有意义,否则会导致管道破裂,向管道中写入数据进程将收到内核传来的SIGPIPE信号(通常时Broken pipe错误)。

练习:父子进程实现通信,父进程循环从终端输入数据,子进程循环打印数据,当输入quit结束。

提示:不需要加同步机制, 因为pipe无数据时读会阻塞。

考虑:创建管道是在fork之前还是之后?

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>int main(int argc, char const *argv[])
{pid_t pid;char buf[32] = "";int fd[2] = {0};if (pipe(fd) < 0){perror("pipe err");return -1;}printf("%d %d\n", fd[0], fd[1]);pid = fork();if (pid < 0){perror("fokr err");return -1;}else if (pid == 0){//循环打印while (1){//read读管道中内容read(fd[0], buf, 32);//判断quit就breakif (strcmp(buf, "quit") == 0)break;//printf打印到终端printf("%s\n", buf);}}else{//循环输入while (1){//先scanfscanf("%s", buf);//将写入的buf中内容用write写进管道write(fd[1], buf, 32);//write(fd[1], buf, strlen(buf)+1);//+1是为了把\0也写进管道//判断quit就breakif (strcmp(buf, "quit") == 0)break;}}//回收子进程wait(NULL);return 0;
}

3. 有名管道

3.1 特点

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

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

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

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

5) 不支持如lseek() 操作

3.2 函数接口

int mkfifo(const char *filename,mode_t mode);
功能:创健有名管道
参数:filename:有名管道文件名mode:权限
返回值:成功:0失败:-1,并设置errno号注意对错误的处理方式:
如果错误是file exist时,注意加判断,如:if(errno == EEXIST)

注:函数只是在路径下创建管道文件,往管道中写的数据依然写在内核空间。

先创建有名管道,然后用文件IO操作:打开、读写和关闭。

3.3 注意事项

1) 只写方式打开阻塞,一直到另一个进程把读打开

2) 只读方式打开阻塞,一直到另一个进程把写打开

3) 可读可写,如果管道中没有数据,读阻塞

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>int main(int argc, char const *argv[])
{int fd;char buf[32] = "";if (mkfifo("./fifo", 0666) < 0){if (errno == EEXIST)printf("file exist\n");else{perror("mkfifo err");return -1;}}printf("mkfifo success\n");//打开管道文件fd = open("./fifo", O_RDWR);if (fd < 0){perror("open err");return -1;}//读写文件write(fd, "hello", 5);read(fd, buf, 5);printf("%s\n", buf);return 0;
}

练习:通过两个进程实现cp功能。

./input srcfile

./output destfile

input.c 读源文件

//创建有名管道

//打开管道文件,打开源文件

//循环读源文件,再把内容写道管道

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>int main(int argc, char const *argv[])
{int fd_fifo, fd_file;char buf[32] = "";ssize_t s;if (mkfifo("./fifo", 0666) < 0){if (errno == EEXIST)printf("file exist\n");else{perror("mkfifo err");return -1;}}printf("mkfifo success\n");//打开管道文件fd_fifo = open("./fifo", O_WRONLY);if (fd_fifo < 0){perror("open fifo err");return -1;}fd_file = open(argv[1], O_RDONLY);if (fd_file < 0){perror("open file err");return -1;}//读写while (1){//从文件读到buf,判断读不到就结束s=read(fd_file,buf,32);if(s==0)break;//把buf中数据写到有名管道中write(fd_fifo,buf,s);}close(fd_fifo);close(fd_file);return 0;
}

output.c 写目标文件

//创建有名管道

//打开有名管道,打开目标文件

//循环读管道,把内容写到目标文件

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>int main(int argc, char const *argv[])
{int fd_fifo, fd_file;char buf[32] = "";ssize_t s;if (mkfifo("./fifo", 0666) < 0){if (errno == EEXIST)printf("file exist\n");else{perror("mkfifo err");return -1;}}printf("mkfifo success\n");//打开管道文件fd_fifo = open("./fifo", O_RDONLY);if (fd_fifo < 0){perror("open fifo err");return -1;}fd_file = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd_file < 0){perror("open file err");return -1;}//读写while (1){//从有名管道中读数据到buf,判断s = read(fd_fifo, buf, 32);if (s == 0)break;//把buf中数据写到目标文件write(fd_file, buf, s);}close(fd_fifo);close(fd_file);return 0;
}

3.4 有名管道和无名管道的区别

无名管道

有名管道

使用场景

有亲缘关系的进程使用

不相干的两个进程使用

特点

半双工通讯方式

固定读端fd[0]和写端fd[1]

看做一种特殊文件,可以通过文件IO操作

在文件系统中会存在管道文件,数据存放在内核空间中

通过文件IO进行操作

不支持lseek操作,也遵循先进先出

函数

pipe()

直接read、write

mkfifo()

先打开open,再对管道文件read、write读写

读写特性

当管道中无数据,读阻塞

当管道中写满,写阻塞,直到有4K空间才可以写

读端关闭,写会导致管道破裂

只读方式打开会阻塞,直到另一个进程把写打开

只写方式打开会阻塞,直到另一个进程把读打开

可读可写,如果管道中没有数据,读阻塞。

4. 信号

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

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

4.1概念

1)信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式

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

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

信号的生命周期:

4.2信号的响应方式

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

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

3)执行缺省操作:Linux对每种信号都规定了默认操作

4.3 信号种类

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 产生,用于请求停止进程。

4.4 函数接口

4.4.1 信号发送和挂起

#include <signal.h>
int kill(pid_t pid, int sig);
功能:信号发送
参数:pid:指定进程sig:要发送的信号
返回值:成功 0     失败 -1int raise(int sig);
功能:进程向自己发送信号
参数:sig:信号
返回值:成功 0   失败 -1相当于:kill(getpid(),sig);int pause(void);
功能:用于将调用进程挂起,直到收到信号为止。
#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>int main(int argc, char const *argv[])
{// //kill(getpid(), SIGKILL); //给指定进程发送信号,此例子指定当前进程// raise(SIGKILL);            //给自己发信号// while (1)//     ;pause(); //将进程挂起,直到收到信号为止,作用类似死循环但是不占用CPU。printf("hello\n");return 0;
}

4.4.2 定时器

unsigned int alarm(unsigned int seconds)功能:在进程中设置一个定时器。当定时器指定的时间到了时,它就向进程发送SIGALARM信号。
参数:seconds:定时时间,单位为秒
返回值:如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。
注意:一个进程只能有一个闹钟时间。如果在调用alarm时已设置过闹钟时间,则之前的闹钟时间被新值所代替。
常用操作:取消定时器alarm(0),返回旧闹钟余下秒数。
#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>int main(int argc, char const *argv[])
{printf("%d\n", alarm(10));sleep(1);printf("%d\n", alarm(3));pause(); //将进程挂起,直到收到信号为止,作用类似死循环但是不占用CPU。return 0;
}

系统默认对SIGALRM(闹钟到点后内核发送的信号)信号的响应: 如果不对SIGALRM信号进行捕捉或采取措施,默认情况下,闹钟响铃时刻会退出进程。例如:

#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>int main(int argc, char const *argv[])
{printf("%d\n", alarm(3));while(1);  //闹钟响铃后结束进程return 0;
}

4.4.3 信号处理函数signal()

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:信号处理函数
参数:signum:要处理的信号handler:信号处理方式SIG_IGN:忽略信号  (忽略 ignore)SIG_DFL:执行默认操作 (默认 default)handler:捕捉信号 (handler为函数名,可以自定义)void handler(int sig){} //函数名可以自定义, 参数为要处理的信号返回值:成功:设置之前的信号处理方式失败:-1

补充:typedef给数据类型重命名

#include <stdio.h>//给普通数据类型int重命名
typedef int size4;        //给指针类型int* 重命名
typedef int *int_p;       //给数组类型int [10]重命名
typedef int intArr10[10]; //给函数指针void (*)()重命名
typedef void (*fun_p)(); void fun()
{printf("fun\n");
}int main(int argc, char const *argv[])
{size4 a = 10;             //相当于int aint_p p = &a;             //相当于int* pintArr10 arr = {1, 2, 3}; //相当于int arr[10]fun_p fp = fun;           //相当于void (*pf)();printf("%d\n", *p);printf("%d\n", arr[0]);fp();return 0;
}

例子:

#include <signal.h>
#include <stdio.h>
#include <unistd.h>void handler(int sig)
{printf("ctrl+C\n");
}int main(int argc, char const *argv[])
{//signal(SIGINT,SIG_IGN);   //忽略信号//signal(SIGINT,SIG_DFL);   //按默认方式处理信号signal(SIGINT,handler);     //捕捉信号,比较常用的方式while(1); //为了不让进程结束return 0;
}

练习:

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

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

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

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

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

分析:司机(父进程)、售票员(子进程)

售票员:捕捉:SIGINT SIGQUIT SIGUSR1

忽略:SIGTSTP

司机:捕捉:SIGUSR1 SIGUSR2 SIGTSTP

忽略:SIGINT SIGQUIT

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>pid_t pid;void saler(int sig)
{if (sig == SIGINT)kill(getppid(),SIGUSR1);else if(sig == SIGQUIT)kill(getppid(),SIGUSR2);else if(sig == SIGUSR1){printf("pls get off the bus!\n");exit(0);}
}void driver(int sig)
{if(sig == SIGUSR1)printf("[driver] let's gogogo!\n");else if(sig == SIGUSR2)printf("stop the bus!\n");else if(sig == SIGTSTP){kill(pid,SIGUSR1);wait(NULL);         //等售票员下车以后再下车exit(0);        }
}int main(int argc, char const *argv[])
{if ((pid = fork()) < 0){perror("fokr err");return -1;}else if (pid == 0){printf("hi,i am saler %d\n", getpid());signal(SIGINT,saler);signal(SIGQUIT,saler);signal(SIGUSR1,saler);signal(SIGTSTP,SIG_IGN);}else{printf("hi, i am driver %d\n", getpid());signal(SIGUSR1,driver);signal(SIGUSR2,driver);signal(SIGTSTP,driver);signal(SIGINT,SIG_IGN);signal(SIGQUIT,SIG_IGN);}while(1)pause(); //不能只发送一个信号进程就结束了,所以可以循环挂起,不占用CPU。return 0;
}

5. 共享内存

5.1特点

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

2)为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间。

3)进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。

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

5.2步骤

a.创建key值

b.创建或者打开共享内存

c.映射共享内存到用户空间

d.撤销映射

e.删除共享内存

5.3函数接口

key_t ftok(const char *pathname, int proj_id);
功能:创建出来的具有唯一映射关系的一个key值,帮助操作系统用来标识一块共享内存
参数:Pathname:已经存在的可访问文件的名字Proj_id:一个字符(因为只用低8位)
返回值:成功:key值失败:-1int shmget(key_t key, size_t size, int shmflg);
功能:创建或打开共享内存
参数:key  键值size   共享内存的大小shmflg   IPC_CREAT|IPC_EXCL|0777注意:shmflg为IPC_CREAT|IPC_EXCL|0777这种形式代表创建共享内存,如果只有权限代表打开共享内存返回值:成功  shmid出错    -1void  *shmat(int  shmid,const  void  *shmaddr,int  shmflg); //attaches
功能:映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
参数: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
功能:取消映射
参数:要取消的地址
返回值:成功0  失败的-1int  shmctl(int  shmid,int  cmd,struct shmid_ds *buf); //control
功能:(删除共享内存),对共享内存进行各种操作
参数: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 <sys/shm.h>
#include <errno.h>
#include <string.h>int main(int argc, char const *argv[])
{key_t key;int shmid;char *p;//创建key值,key值由指定文件的inode号和字符的ascii码组合而成key = ftok("./shm.c", '6');if (key < 0){perror("ftok err");return -1;}printf("key: %#x\n", key);//打开或创建共享内存shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666); //创建共享内存if (shmid <= 0){if (errno == EEXIST)shmid = shmget(key, 128, 0666); //直接打开已经存在的共享内存else{perror("shmget err");return -1;}}printf("shmid: %d\n", shmid);//映射共享内存p = (char *)shmat(shmid, NULL, 0);if (p == (char *)-1){perror("shmat err");return -1;}//操作共享内存strcpy(p, "hello");printf("%s\n", p);//取消映射shmdt(p);// //删除共享内存// shmctl(shmid,IPC_RMID,NULL);return 0;
}

5.4命令

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

ipcrm-mshmid:删除指定共享内存

注意:可能不能删除还存在进程使用的共享内存,这时候可以用kill杀死多余进程,再使用ipcs查看。

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

提示:为了共享标志位可以和buf封装到一个结构体里作为共享内存。

struct msg

{

int flag;

char buf[32];

};

输入进程:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <string.h>typedef struct msg
{int flag;char buf[32];
} msg_t;int main(int argc, char const *argv[])
{key_t key;int shmid;msg_t *p;//创建key值,key值由指定文件的inode号和字符的ascii码组合而成key = ftok("./shm.c", '6');if (key < 0){perror("ftok err");return -1;}printf("key: %#x\n", key);//打开或创建共享内存shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666); //创建共享内存if (shmid <= 0){if (errno == EEXIST)shmid = shmget(key, 128, 0666); //直接打开已经存在的共享内存else{perror("shmget err");return -1;}}printf("shmid: %d\n", shmid);//映射共享内存p = (msg_t *)shmat(shmid, NULL, 0);if (p == (msg_t *)-1){perror("shmat err");return -1;}p->flag = 0; //初始化//操作共享内存while (1){scanf("%s", p->buf);p->flag = 1;if (strcmp(p->buf, "quit") == 0)break;}//取消映射shmdt(p);// //删除共享内存// shmctl(shmid,IPC_RMID,NULL);return 0;
}

输出进程:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <string.h>typedef struct msg
{int flag;char buf[32];
} msg_t;int main(int argc, char const *argv[])
{key_t key;int shmid;msg_t *p;//创建key值,key值由指定文件的inode号和字符的ascii码组合而成key = ftok("./shm.c", '6');if (key < 0){perror("ftok err");return -1;}printf("key: %#x\n", key);//打开或创建共享内存shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666); //创建共享内存if (shmid <= 0){if (errno == EEXIST)shmid = shmget(key, 128, 0666); //直接打开已经存在的共享内存else{perror("shmget err");return -1;}}printf("shmid: %d\n", shmid);//映射共享内存p = (msg_t *)shmat(shmid, NULL, 0);if (p == (msg_t *)-1){perror("shmat err");return -1;}//操作共享内存p->flag = 0;while (1){if (strcmp(p->buf, "quit") == 0)break;if (p->flag == 1){printf("%s\n", p->buf);p->flag = 0;}}//取消映射shmdt(p);// //删除共享内存// shmctl(shmid,IPC_RMID,NULL);return 0;
}

6. 信号灯

线程:全局变量,或者通过信号量来实现同步

初始化:sem_init(&sem,0,0);

申请资源:sem_wait(&sem);p操作-1

释放资源:sem_post(&sem);v操作+1

6.1 特点

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

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

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

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

6.2步骤

(1) 创建或者打开信号灯集:semget

(2) 初始化信号灯:semctl

(3) PV操作:semop

(4) 删除信号灯集:semctl

6.3 命令

ipcs -s查看系统中信号灯集

ipcrm -s semid:删除信号灯集

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

6.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失败:-1struct 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);

创建使用信号灯

#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/sem.h>
#include <errno.h>union semun
{int val;
};int main(int argc, char const *argv[])
{int semid;key_t key;if ((key = ftok("./sem.c", 66)) < 0){perror("ftok err");return -1;}printf("key: %#x\n", key);//创建信号灯集semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666);if (semid <= 0){if (errno == EEXIST)semid = semget(key, 2, 0666);   //直接打开信号灯集else{perror("semget err");return -1;}}else  //确保对信号灯集中的信号灯只初始化一次,下次执行程序还继续获得上一次的值。如果想修改初值,需要删除信号灯然后重新打开。{union semun sem;sem.val = 10;semctl(semid,0,SETVAL,sem); //对编号为0的信号灯初值设为10sem.val = 0;semctl(semid,1,SETVAL,sem); //对编号为1的信号灯初值设为0}printf("semid: %d\n", semid);//获取信号灯初值printf("%d\n",semctl(semid,0,GETVAL)); //获取编号为0的信号灯的值printf("%d\n",semctl(semid,1,GETVAL)); //获取编号为1的信号灯的值//pv操作struct sembuf buf;buf.sem_num=0;buf.sem_op=-1; //申请资源,P操作,-1buf.sem_flg=0; //阻塞semop(semid,&buf,1);buf.sem_num=1;buf.sem_op=1; //释放资源,V操作,+1buf.sem_flg=0;semop(semid,&buf,1);printf("%d\n",semctl(semid,0,GETVAL)); //获取编号为0的信号灯的值printf("%d\n",semctl(semid,1,GETVAL)); //获取编号为1的信号灯的值// //删除信号灯集// semctl(semid,0,IPC_RMID); //指定任意一个信号灯就可以,会删除整个信号灯集。return 0;
}

函数操作:

#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/sem.h>
#include <errno.h>union semun {int val;
};void seminit(int semid, int num, int val) //初始化
{union semun sem;sem.val = val;semctl(semid, num, SETVAL, sem); //对编号为num的信号灯初值设为val
}void sem_op(int semid, int num,int op)
{struct sembuf buf;buf.sem_num = num;buf.sem_op = op;buf.sem_flg =0;semop(semid,&buf,1);
}int main(int argc, char const *argv[])
{int semid;key_t key;if ((key = ftok("./sem.c", 66)) < 0){perror("ftok err");return -1;}printf("key: %#x\n", key);//创建信号灯集semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666);if (semid <= 0){if (errno == EEXIST)semid = semget(key, 2, 0666); //直接打开信号灯集else{perror("semget err");return -1;}}else //确保对信号灯集中的信号灯只初始化一次,下次执行程序还继续获得上一次的值。如果想修改初值,需要删除信号灯然后重新打开。{seminit(semid, 0, 10);seminit(semid, 1, 0);}printf("semid: %d\n", semid);//获取信号灯初值printf("%d\n", semctl(semid, 0, GETVAL)); //获取编号为0的信号灯的值printf("%d\n", semctl(semid, 1, GETVAL)); //获取编号为1的信号灯的值//pv操作sem_op(semid,0,-1);sem_op(semid,1,1);printf("%d\n", semctl(semid, 0, GETVAL)); //获取编号为0的信号灯的值printf("%d\n", semctl(semid, 1, GETVAL)); //获取编号为1的信号灯的值// //删除信号灯集// semctl(semid,0,IPC_RMID); //指定任意一个信号灯就可以,会删除整个信号灯集。return 0;
}

信号灯加到共享内存

输入功能程序:

#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/sem.h>
#include <errno.h>
#include <string.h>
#include <sys/shm.h>union semun {int val;
};void seminit(int semid, int num, int val) //初始化
{union semun sem;sem.val = val;semctl(semid, num, SETVAL, sem); //对编号为num的信号灯初值设为val
}void sem_op(int semid, int num, int op)
{struct sembuf buf;buf.sem_num = num;buf.sem_op = op;buf.sem_flg = 0;semop(semid, &buf, 1);
}int main(int argc, char const *argv[])
{int semid;int shmid;char *p;key_t key;if ((key = ftok("./sem.c", 66)) < 0){perror("ftok err");return -1;}printf("key: %#x\n", key);//创建/打开共享内存shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);if (shmid <= 0){if (errno == EEXIST)shmid = shmget(key, 128, 0666);else{perror("shmget err");return -1;}}printf("shmid: %d\n", shmid);//创建信号灯集semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666);if (semid <= 0){if (errno == EEXIST)semid = semget(key, 2, 0666); //直接打开信号灯集else{perror("semget err");return -1;}}else //确保对信号灯集中的信号灯只初始化一次,下次执行程序还继续获得上一次的值。如果想修改初值,需要删除信号灯然后重新打开。{seminit(semid, 0, 0);}printf("semid: %d\n", semid);//共享内存映射p = (char *)shmat(shmid, NULL, 0);if (p == (char *)-1){perror("shmat err");return -1;}while (1){scanf("%s", p);//释放资源sem_op(semid,0,1);if (strcmp(p, "quit")==0)break;}shmdt(p);shmctl(shmid,IPC_RMID,NULL);// //删除信号灯集// semctl(semid,0,IPC_RMID); //指定任意一个信号灯就可以,会删除整个信号灯集。return 0;
}

out.c

#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/sem.h>
#include <errno.h>
#include <string.h>
#include <sys/shm.h>union semun {int val;
};void seminit(int semid, int num, int val) //初始化
{union semun sem;sem.val = val;semctl(semid, num, SETVAL, sem); //对编号为num的信号灯初值设为val
}void sem_op(int semid, int num, int op)
{struct sembuf buf;buf.sem_num = num;buf.sem_op = op;buf.sem_flg = 0;semop(semid, &buf, 1);
}int main(int argc, char const *argv[])
{int semid;int shmid;char *p;key_t key;if ((key = ftok("./sem.c", 66)) < 0){perror("ftok err");return -1;}printf("key: %#x\n", key);//创建/打开共享内存shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);if (shmid <= 0){if (errno == EEXIST)shmid = shmget(key, 128, 0666);else{perror("shmget err");return -1;}}printf("shmid: %d\n", shmid);//创建信号灯集semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0666);if (semid <= 0){if (errno == EEXIST)semid = semget(key, 2, 0666); //直接打开信号灯集else{perror("semget err");return -1;}}else //确保对信号灯集中的信号灯只初始化一次,下次执行程序还继续获得上一次的值。如果想修改初值,需要删除信号灯然后重新打开。{seminit(semid, 0, 0);}printf("semid: %d\n", semid);//共享内存映射p = (char *)shmat(shmid, NULL, 0);if (p == (char *)-1){perror("shmat err");return -1;}while (1){sem_op(semid,0,-1); //申请资源if (strcmp(p, "quit")==0)break;printf("%s\n", p);}shmdt(p);shmctl(shmid, IPC_RMID, NULL);// //删除信号灯集// semctl(semid,0,IPC_RMID); //指定任意一个信号灯就可以,会删除整个信号灯集。return 0;
}

7.消息队列:messagequeue

传统:无名管道、有名管道、信号

systemV:共享内存、信号灯集、消息队列

按消息的类型添加或读取消息

队列原则

7.1特点

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

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

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

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

7.2步骤

(1) 产生key值ftok

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

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

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

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

7.3命令

ipcs -q:查看系统中的消息队列

ipcrm -q msgid:删除消息队列

7.4函数接口

int msgget(key_t key, int flag);
功能:创建或打开一个消息队列
参数:  key值flag:创建消息队列的权限IPC_CREAT|IPC_EXCL|0666返回值:成功:msgid失败:-1int msgsnd(int msqid, const void *msgp, size_t size, int flag); 
功能:添加消息
参数:msqid:消息队列的IDmsgp:指向消息的指针。常用消息结构msgbuf如下:struct msgbuf{long mtype;          //消息类型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);
功能:读取消息
参数:msgid:消息队列的IDmsgp:存放读取消息的空间size:接受的消息正文的字节数(sizeof(msgp)-sizeof(long))msgtype:0:接收消息队列中第一个消息。大于0:接收消息队列中第一个类型为msgtyp的消息.小于0:接收消息队列中类型值不小于msgtyp的绝对值且类型值又最小的消息。flag:0:若无消息函数会一直阻塞IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG
返回值:成功:接收到的消息的长度失败:-1int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );
功能:对消息队列的操作,删除消息队列
参数:msqid:消息队列的队列IDcmd:IPC_STAT:读取消息队列的属性,并将其保存在buf指向的缓冲区中。IPC_SET:设置消息队列的属性。这个值取自buf参数。IPC_RMID:从系统中删除消息队列。buf:消息队列缓冲区
返回值:成功:0失败:-1用法:msgctl(msgid, IPC_RMID, NULL)
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <errno.h>struct msgbuf
{long type; //必须有而且必须是long类型,表示消息类型,值>0int num;   //正文随便定义char ch;
};int main(int argc, char const *argv[])
{key_t key;int msgid;key = ftok("msg.c", 99);if (key < 0){perror("ftok err");return -1;}printf("key:%#x\n", key);//打开消息队列msgid = msgget(key, IPC_CREAT | IPC_EXCL | 0666);if (msgid <= 0){if (errno == EEXIST)msgid = msgget(key, 0666);else{perror("msgget err");return -1;}}printf("msgid:%d\n", msgid);//添加消息struct msgbuf msg;msg.type = 10;msg.num = 100;msg.ch = 'a';msgsnd(msgid, &msg, sizeof(msg) - sizeof(long), 0); //0:阻塞等发完才返回msg.type = 20;msg.num = 200;msg.ch = 'b';msgsnd(msgid, &msg, sizeof(msg) - sizeof(long), 0);//读取消息struct msgbuf m;msgrcv(msgid, &m, sizeof(m) - sizeof(long), 20, 0); //读取消息队列中类型为20的第一个消息printf("%d %c\n", m.num, m.ch);msgctl(msgid,IPC_RMID,NULL);return 0;
}

两个进程分别发和收:


http://www.ppmy.cn/ops/150943.html

相关文章

《AI赋能鸿蒙Next,开启智能关卡设计新时代》

在游戏开发领域&#xff0c;关卡设计是至关重要的一环&#xff0c;它直接影响着玩家的游戏体验和沉浸感。而随着人工智能技术的飞速发展&#xff0c;结合鸿蒙Next系统的强大功能&#xff0c;为游戏的智能关卡设计带来了全新的思路和方法。 利用AI学习玩家行为模式 在鸿蒙Next…

昇腾部署onnx模型问题总结

使用ATC转换onnx模型出错 报错信息 ERROR:edge_agent:Run command return fail, COMMAND"atc --framework5 --model/deploy/in/fastsam/FastSAM-x.onnx --soc-versionAscend910B4 --output/deploy/models/modelname/1/model", RET255, STDOUT"ATC start worki…

检验统计量与p值笔记

一、背景 以雨量数据为例&#xff0c;当获得一个站点一年的日雨量数据后&#xff0c;我们需要估计该站点的雨量的概率分布情况&#xff0c;因此我们利用有参估计的方式如极大似然法估计得到了假定该随机变量服从某一分布的参数&#xff0c;从而得到该站点的概率密度函数&#x…

如何在Ubuntu上安装Cmake

前言 ​ 本文主要阐述如何在Ubuntu22.04上面安装cmake&#xff0c;具体可看下面的操作。 正文 一、环境 Ubuntu22.04 cmake-3.31.4.tar.gz 二、步骤 参考这个方案&#xff1a; 【运维】Ubuntu如何安装最新版本的Cmake&#xff0c;编译安装Cmake&#xff0c;直接命令安装…

大疆最新款无人机发布,可照亮百米之外目标

近日&#xff0c;DJI 大疆发布全新小型智能多光旗舰 DJI Matrice 4 系列&#xff0c;包含 Matrice 4T 和 Matrice 4E 两款机型。DJI Matrice 4E 价格为27888 元起&#xff0c;DJI Matrice 4T价格为38888元起。 图片来源&#xff1a;大疆官网 DJI Matrice 4E DJI Matrice 4T D…

解决:ubuntu22.04中IsaacGymEnv保存视频报错的问题

1. IsaacGymEnvs项目介绍 IsaacGymEnvs&#xff1a;基于NVIDIA Isaac Gym的高效机器人训练环境 IsaacGymEnvs 是一个基于 NVIDIA Isaac Gym 的开源 Python 环境库&#xff0c;专为机器人训练提供高效的仿真环境。Isaac Gym 是由 NVIDIA 开发的一个高性能物理仿真引擎&#xf…

从零深度学习:(2)最小二乘法

今天我们从比较简单的线性回归开始讲起&#xff0c;还是一样我们先导入包 import numpy as np import torch import matplotlib as mpl import matplotlib.pyplot as plt a torch.arange(1,5).reshape(2,2).float() a 我们利用刚刚导入的画图的包将这两个点画出来&#xff0…

【云岚到家】-day02-客户管理-认证授权

第二章 客户管理 1.认证模块 1.1 需求分析 1.基础概念 一般情况有用户交互的项目都有认证授权功能&#xff0c;首先我们要搞清楚两个概念&#xff1a;认证和授权 认证: 就是校验用户的身份是否合法&#xff0c;常见的认证方式有账号密码登录、手机验证码登录等 授权:则是该用…