Linux进程间通信(IPC)
管道、消息队列、共享内存、信号量、信号
一、管道
定义:管道,通常指无名管道,是 UNIX 系统IPC最古老的形式。
1.1 无名管道
特点:
- 半双工。(同一时间,数据单向流通)
- 只应用在父子或者兄弟进程之间。
- 数据读取后,管道中的数据即不存在了。
//头文件
##include <unistd.h>
//主函数
int pipe(int fd[2]);
// 返回值:success返回0,error返回-1
参数说明:
当一个管道建立时,它会创建两个文件描述符:fd[0]
为读而打开,fd[1]
为写而打开。
1.2 命名管道(FIFO)
特点:
- FIFO可以在无关进程之间交换数据。
- FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
//头文件
##include <sys/stat.h>
//主函数
int mkfifo(const char *pathname, mode_t mode);
// 返回值:success返回0,error返回-1
参数说明:
其中的 mode 参数与open函数中的 mode 相同。一旦创建了一个 FIFO,就可以用一般的文件I/O函数操作它。
应用区别
无名管道:pipe(fd) 和 fork() 联用;
有名管道:mkfifo(path, mode) 和 open() 联用;
二、消息队列
消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
特点:
- 消息具有特定的格式以及特定的优先级。
- 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
- 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
前提结构体
// 消息结构
struct msg_form {
long mtype;
char mtext[256];
};
最简单的消息队列使用流程
- ftok函数生成键值;
- msgget函数创建或打开消息队列;
- msgsnd函数添加消息
- msgrcv函数读取消息
- msgctl删除消息队列
// 头文件
#include <sys/ipc.h>
//主函数
key_t ftok(const char*path,int id);
//返回 success 返回 key值, error返回 -1
参数说明:
path 为 已经存在的路径名
id 为 0~255之间的数值
举例:
key_t key =ftok("/tmp",66);
// 头文件
#include <sys/msg.h>
//主函数
int msgget(key_t key, int flag); // 成功返回队列ID,失败返回-1
int msgsnd(int msqid, const void *ptr, size_t size, int flag);//成功返回0,失败返回-1
int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);//成功返回消息数据的长度,失败返回-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf);//成功返回0,失败返回-1
shell窗口指令
ls -a查看文件前的点
ls -i查看文件索引节点号
例如:文件索引节点号 65538 ,换成16进制为 0x010002,而你指定的id号为38,16进制为0x26,key_t返回值为0x26010002
三、共享内存
共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。
特点:
- 进程是直接对内存进行存取。
- 因为多个进程可以同时操作,所以需要进行同步。
- 信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。
常见内存交流的流程:
- shmget函数创建或获取一个共享内存;
- shmat函数连接共享内存到当前进程的地址空间;
- shmdt函数断开共享内存;
- shmctl函数删除共享内存;
//头文件
#include <sys/shm.h>
//主函数
int shmget(key_t key, size_t size, int flag); // 成功返回共享内存ID,失败返回-1
void *shmat(int shmid, const void *shmaddr, int shmflag); // 成功返回指向共享内存的指针,失败返回-1
int shmdt(void *addr); // 成功返回0,失败返回-1
int shmctl(int shm_id, int cmd, struct shmid_ds *buf); //成功返回0,失败返回-1
参数说明:
1、int shmget(key_t key, size_t size, int flag);
size必须1M以上;
// 例如:
shmget(key,1024*4,IPC_CREAT|0666);
2、void *shmat(int shmid, const void *shmaddr, int shmflag);
shmid:共享内存地址
shmaddr:取0,内核决定地址段
shmflag:取0,可读可写
3、int shmdt(void *addr);
addr:shmat 返回共享内存指针;
4、int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
cmd:一般取IPC_RMID(删去);
buf:取0;
// 例如:
shmctl(shmid,IPC_RMID,0);
shell窗口指令
ipcs -m:查看当前有哪些共享内存
ipcrm -m:删掉对应id号的共享内存
缺点:需要控制共享内存写入顺序。
解决方法:信号量来控制。
四、信号量
它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
特点:
- 信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
- 信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
- 每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
- 支持信号量组。
类比房间和锁的关系:
钥匙:信号量
房间:临界资源
信号量集:linux下信号量总体
p操作:拿锁
v操作:放回锁
常见信号量操作的流程:
1、创建或获取一个信号量组;
2、初始化信号量;
3、对信号量组进行操作,改变信号量的值;
// 头文件
#include <sys/sem.h>
int semget(key_t key, int num_sems, int sem_flags);// 若成功返回信号量集ID,失败返回-1
int semctl(int semid, int sem_num, int cmd, ...);// 初始化信号量的相关信息
int semop(int semid, struct sembuf semoparray[], size_t numops);// 成功返回0,失败返回-1
参数说明:
1、int semget(key_t key, int num_sems, int sem_flags);
key:与消息队列和共享内存一样
num_sems:信号量个数
sem_flags:IPC_CREAT|0666
2、int semctl(int semid, int sem_num, int cmd, …);
semid:semget返回值semid
sem_num:信号组中对应的序号,0~sem_nums-1
cmd:
- SETVAL:用于初始化信号量为一个已知的值。所需要的值作为联合semun的val成员来传递。在信号量第一次使用之前需要设置信号量。
- IPC_RMID:删除一个信号量集合。
- union semun {
int val; // SETVAL使用的值
struct semid_ds *buf; // IPC_STAT、IPC_SET 使用缓存区
unsigned short *array; // GETALL,、SETALL 使用的数组
struct seminfo *__buf; // IPC_INFO(Linux特有) 使用缓存区
};
3、int semop(int semid, struct sembuf semoparray[], size_t numops);
semoparray[]:
- struct sembuf {
short sem_num; // 信号量组中对应的序号,0~sem_nums-1
short sem_op; // 信号量值在一次操作中的改变量
short sem_flg; // IPC_NOWAIT, SEM_UNDO
};
numops:信号操作结构体数量。numops>>=1
五、信号
实际信号是软中断,许多重要的程序都需要处理信号。信号,为 Linux 提供了一种处理异步事件的方法。比如,终端用户输入了 ctrl+c 来中断程序,会通过信号机制停止一个程序。
5.1 信号的名字和编号
每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGIO ”、“SIGCHLD”等等。
信号定义在signal.h头文件中,信号名都定义为正整数。
具体的信号名称可以使用kill -l来查看信号的名字以及序号,信号是从1开始编号的,不存在0号信号。kill对于信号0又特殊的应用。
kill -l:查看全部信号的编号(signum)
kill -9:杀死进程
kill -SIGKILL + pid号:杀死进程
5.2 信号处理函数的注册和发送
信号处理函数的注册不只一种方法,分为简单版和高级版
简单版:signal函数
高级版:sigaction函数
信号处理发送函数
信号发送函数也不止一个,同样分为简单版和高级版
简单版:kill函数
高级版:sigqueue函数
信号注册函数——简单版signal
// 头文件
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
- typedef void (*sighandler_t)(int);中断函数的原型中,有一个参数是 int类型,显然也是信号产生的类型,方便使用一个函数来处理多个信号。
- 对于sighandler_t signal(int signum,sighandler_t handler);函数来说,signum 显然是信号的编号,handler 是中断函数的指针。
// 例如:
signal(SIGIO, handler); //SIGIO文件描述符准备就绪, 可以开始进行输入/输出操作
signal(SIGUSR1, handler); //SIGUSR1留给用户使用
信号发送函数——简单版kill
// An highlighted block
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
参数说明:
pid 就是接受者的 pid,sig 则是发送的信号的类型
关于 kill 函数,还有一点需要额外说明,上面的程序限定了 pid 必须为大于0的正整数,其实 kill 函数传入的 pid 可以是小于等于0的整数。
pid > 0:将发送个该 pid 的进程
pid == 0:将会把信号发送给与发送进程属于同一进程组的所有进程,并且发送进程具有权限想这些进程发送信号。
pid < 0:将信号发送给进程组ID 为 pid 的绝对值得,并且发送进程具有权限向其发送信号的所有进程
pid == -1:将该信号发送给发送进程的有权限向他发送信号的所有进程。(不包括系统进程集中的进程)
信号注册函数——高级版sigaction
// 头文件
#include <signal.h>
//主函数
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
//结构体
struct sigaction {
void (*sa_handler)(int); //信号处理程序,不接受额外数据,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只能任选其一
参数说明:
- 第一个参数signum应该就是注册的信号的编号;第二个参数act如果不为空说明需要对该信号有新的配置;第三个参数oldact如果不为空,那么可以对之前的信号配置进行备份,以方便之后进行恢复。
- struct sigaction *oldact:保存上一次信息。
- (*sa_sigaction)(int, siginfo_t *, void *);
// siginfo_t 结构体
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 */
}
- 总结需要配置:const struct sigaction *act;(*sa_sigaction)(int, siginfo_t *, void *);sa_flags;
信号发送函数——高级版sigqueue
// 头文件
#include <signal.h>
//主函数
int sigqueue(pid_t pid, int sig, const union sigval value);
//结构体
union sigval {
int sival_int;
void *sival_ptr;
};
使用这个函数之前,必须要有几个操作需要完成
- 使用 sigaction 函数安装信号处理程序时,制定了 SA_SIGINFO 的标志。 sigaction 结构体中的
sa_sigaction 成员提供了信号捕捉函数。如果实现的时 sa_handler 成员,那么将无法获取额外携带的数据。 - sigqueue 函数只能把信号发送给单个进程,可以使用 value 参数向信号处理程序传递整数值或者指针值。
sigqueue 函数不但可以发送额外的数据,还可以让信号进行排队(操作系统必须实现了 POSIX.1的实时扩展),对于设置了阻塞的信号,使用 sigqueue 发送多个同一信号,在解除阻塞时,接受者会接收到发送的信号队列中的信号,而不是直接收到一次。
atoi():字符型转换成整型;
int sprintf(char *str, const char *format, …):format内容写入str字符串里。
管道举例
无名管道
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
int fd[2];
pid_t pid;
char readBuff[128];
if(pipe(fd)<0){
printf("pipe create failure\n");
}
if((pid=fork())<0){
printf("pipe fork failure\n");
}
else if(pid>0){
sleep(3);
printf("this is father\n");
close(fd[0]);
write(fd[1],"hello world",strlen("hello world "));
wait();
}else{
printf("this is child\n");
close(fd[1]);
read(fd[0],readBuff,128);
printf("readBuff=%s\n",readBuff);
exit(0);
}
return 0;
}
命名管道——读取
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
char buf[128]={0};
int nread =0;
int count=0;
// int mkfifo(const char *pathname, mode_t mode);
if((mkfifo("./file",0600)== -1) && errno != EEXIST){
printf("mkfifo failure\n");
perror("why");
}else{
if(errno == EEXIST){
printf("file exist\n");
}else{
printf("mkfifo success\n");
}
}
int fd =open("./file",O_RDONLY);
printf("open success\n");
while(count<=12){
nread=read(fd, buf, 30);
printf("read %d byte from fifo, context=%s\n",nread,buf);
count++;
}
close(fd);
return 0;
}
命名管道——写入
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main()
{
char *str="this is message from fifo";
int nwrite =0;
int cnt =0;
int fd =open("./file",O_WRONLY);
printf("open success\n");
while(cnt<=10){
nwrite=write(fd, str, strlen(str));
sleep(1);
cnt++;
}
close(fd);
return 0;
}
消息队列举例
先收再发
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
key_t key;
key=ftok(".",26);
// int msgget(key_t key, int msgflg);
int msgId=msgget(key,IPC_CREAT|0777);
if(msgId == -1){
printf("quene creat failure\n");
}
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf readBuf;
msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);
printf("read from quene:%s\n",readBuf.mtext);
struct msgbuf sendBuf={988,"thank for your reach "};
msgsnd(msgId,&sendBuf,sizeof(sendBuf.mtext),0);
msgctl(msgId,IPC_RMID,NULL);
return 0;
}
先发再收
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
key_t key;
key=ftok(".",26);
// int msgget(key_t key, int msgflg);
int msgId=msgget(key,IPC_CREAT|0777);
if(msgId == -1){
printf("quene creat failure\n");
}
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf sendBuf={888,"this is message from quene "};
msgsnd(msgId,&sendBuf,sizeof(sendBuf.mtext),0);
struct msgbuf readBuf;
msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),988,0);
printf("return from rcvsnd quene:%s\n",readBuf.mtext);
msgctl(msgId,IPC_RMID,NULL);
return 0;
}
共享内存举例
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int shmId;
char *shmaddr;
key_t key;
key=ftok(".",1);
shmId=shmget(key,1024*4,IPC_CREAT|0666);
if(shmId == -1){
printf("shm creat failure\n");
exit(-1);
}
shmaddr=shmat(shmId,0,0);
printf("shmadder creat success\n");
strcpy(shmaddr,"liqingtian haoshuai");
printf("please wait 10sec\n");
sleep(10);
shmdt(shmaddr);
shmctl(shmId,IPC_RMID,0);
return 0;
}
信号量举例
#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, ...);
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) */
};
// struct sembuf{
// unsigned short sem_num; /* semaphore number */
// short sem_op; /* semaphore operation */
// short sem_flg; /* operation flags */
//}
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("getkey\n");
}
void vPutBackKey(int id)
{
struct sembuf set;
set.sem_num=0;//取第一个信号
set.sem_op=1;//拿走
set.sem_flg=SEM_UNDO;//等待放回再拿
semop(id,&set,1);
printf("putbackkey\n");
}
int main()
{
key_t key;
key=ftok(".",1);
int semId=semget(key,1,IPC_CREAT|0666);
union semun initsem;
initsem.val=0;
semctl(semId,0,SETVAL,initsem);
int pid =fork();
if(pid>0){
pGetKey(semId);
printf("this is father\n");
vPutBackKey(semId);
semctl(semId,0,IPC_RMID);
}else if(pid == 0){
// pGetKey(semId);
printf("this is child\n");
vPutBackKey(semId);
// semctl(semId,0,IPC_RMID);
}else{
printf("fork process is error\n");
}
return 0;
}
信号举例
简单版:signal函数
#include<stdio.h>
#include <signal.h>
// typedef void (*sighandler_t)(int);
// sighandler_t signal(int signum, sighandler_t handler);
void handler(int signum)
{
switch (signum){
case 2:
printf("signum =%d,SIGINT\n",signum);
break;
case 3:
printf("signum =%d,SIGQUIT\n",signum);
break;
case 10:
printf("signum =%d,SIGUSR1\n",signum);
break;
}
printf("never quit\n");
}
int main()
{
signal(SIGINT,handler);
signal(SIGQUIT,handler);
signal(SIGUSR1,handler);
while(1);
return 0;
}
简单版:kill函数
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
// int kill(pid_t pid, int sig);
int main(int argc, char **argv)
{
int signum;
int pid;
signum=atoi(argv[1]);
pid=atoi(argv[2]);
printf("pid=%d,signum=%d\n",pid,signum);
kill(pid, signum);
printf("send singal ok\n");
return 0;
}
高级版:sigaction函数
#include <stdio.h>
#include <signal.h>
// int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
/* 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)
{
printf("signum=%d\n",signum);
if(context!=NULL){
printf("get context =%d\n",info->si_int);
printf("get context =%d\n",info->si_value.sival_int);
printf("get from %d\n",info->si_pid);
}
}
int main()
{
struct sigaction act;
act.sa_sigaction=handler;
act.sa_flags=SA_SIGINFO;
printf("pid=%d\n",getpid());
sigaction(SIGUSR1,&act,NULL);
while(1);
return 0;
}
高级版:sigqueue函数
#include <stdio.h>
#include <signal.h>
// int sigqueue(pid_t pid, int sig, const union sigval value);
/* union sigval {
int sival_int;
void *sival_ptr;
};
*/
int main(int argc,char **argv)
{
if(argc!=4){
printf("[Arguments ERROR]\n");
printf("ture Input:\n");
printf("%s <Signal_Number><Target_Pid> <content>\n",argv[0]);
}
int signum;
int pid;
signum=atoi(argv[1]);
pid = atoi(argv[2]);
union sigval value;
value.sival_int =atoi(argv[3]);
printf("send data is %d\n",atoi(argv[3]));
sigqueue(pid,signum,value);
printf("pid=%d\n",getpid());
return 0;
}