Linux 进程通讯
一、概述
目的:为什么进程间需要通信?
- 现在Linux使用的进程间的通信方式包括:
- 1,管道(pipe)和有名管道(FIFO)
- 2,信号(signal)
- 3,消息队列
- 4,共享内存
- 5,信号量
- 6,套接字(socket)
写入数组只能在管道尾部,读取数据只能在管道头部。
三、管道关闭#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> int main() { int pipe_fd[2]; pid_t pid; char buf_r[100]; char* p_wbuf; int r_num; memset(buf_r, 0, sizeof(buf_r)); //创建管道 if (pipe(pipe_fd) < 0) { printf("pipe create error\n"); return -1; } //创建子进程 if ((pid = fork()) == 0) { close(pipe_fd[1]); sleep(2); if((r_num = read(pipe_fd[0], buf_r, 100)) > 0) { printf("%d numbers read from the pipe is %s\n", r_num, buf_r); } close(pipe_fd[0]); exit(0); } else if (pid > 0) { close(pipe_fd[0]); if (write(pipe_fd[1], "Hello", 5) != -1) printf("parent write1 Hello!\n"); if (write(pipe_fd[1], "Pipe", 5) != -1) printf("parent write2 Pipe!\n"); close(pipe_fd[1]); } }五、命名管道(FIFO)
#include <sys/types.h> #include <sys/stat.h> #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define FIFO "/tmp/myfifo" main(int argc,char** argv) { char buf_r[100]; int fd; int nread; /* 创建管道 */ if((mkfifo(FIFO,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST)) printf("cannot create fifoserver\n"); printf("Preparing for reading bytes...\n"); memset(buf_r,0,sizeof(buf_r)); /* 打开管道 */ fd=open(FIFO,O_RDONLY|O_NONBLOCK,0); if(fd==-1) { perror("open"); exit(1); } while(1) { memset(buf_r,0,sizeof(buf_r)); if((nread=read(fd,buf_r,100))==-1) { if(errno==EAGAIN) printf("no data yet\n"); } printf("read %s from FIFO\n",buf_r); sleep(1); } pause(); /*暂停,等待信号*/ unlink(FIFO); //删除文件 }fifo_write.c :
#include <sys/types.h> #include <sys/stat.h> #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define FIFO_SERVER "/tmp/myfifo" main(int argc,char** argv) { int fd; char w_buf[100]; int nwrite; /*打开管道*/ fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0); if(argc==1) { printf("Please send something\n"); exit(-1); } strcpy(w_buf,argv[1]); /* 向管道写入数据 */ if((nwrite=write(fd,w_buf,100))==-1) { if(errno==EAGAIN) printf("The FIFO has not been read yet.Please try later\n"); } else printf("write %s to the FIFO\n",w_buf); }
kill的pid参数有四种不同的情况:
1、pid > 0 : 将信号发送给进程 ID 为 pid 的进程。
2、pid == 0 : 将信号发送给同组的进程。
3、pid < 0 : 将信号发送给其进程组 ID 等于 pid 绝对值的进程。
4、pid == -1: 将信号发送给所有进程。
五、 Alarm
使用 alarm 函数可以设置一个时间值(闹钟时间),当所设置的时间到了时,产生 SIGALRM 信号。如果不捕捉此信号,则默认动作是终止该进程。
#include <unistd.h>
unsigned int alarm(unsigned int seconds)
参数: seconds: 经过了指定的 seconds 秒后会产生信号 SIGALRM 。
注意:每个进程只能有一个闹钟时间。如果在调用 alarm 时, 以前已为该进程设置过闹钟时间,而且他还没有超时,以前登记的闹钟时间则被新值代换。 如果有以前登记的尚未超过的闹钟时间,而这次 seconds 值是 0, 则表示取消以前的闹钟。
六、Pause
pause 函数使调用进程挂起直至捕捉到一个信号。
#include <unistd.h>
int pause(void)
只有执行了一个信号处理函数后,挂起才结束。
1、当系统捕捉到某个信号时,可以忽略该信号或是使用指定的处理函数来处理该信号, 或者使用系统默认的方式。
2、信号处理的主要方法有两种,一种是使用简单的 signal 函数,另一种是使用信号集函数组。
#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int)
如何理解?
typedef void (*sighandler_t)(int)
sighandler_t signal(int signum, sighandler_t handler))
func 可能的值是:
1、SIG_IGN : 忽略此信号
2、SIG_DFL : 按系统默认方式处理
3、信号处理函数名 : 使用该函数处理
例: mysignal.c
#include <signal.h> #include <stdio.h> #include <stdlib.h> void my_func(int sign_no) { if(sign_no==SIGINT) printf("I have get SIGINT\n"); else if(sign_no==SIGQUIT) printf("I have get SIGQUIT\n"); } int main() { printf("Waiting for signal SIGINT or SIGQUIT \n "); /*注册信号处理函数*/ signal(SIGINT, my_func); signal(SIGQUIT, my_func); pause(); exit(0); }
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #define PERM S_IRUSR|S_IWUSR /* 共享内存 */ int main(int argc,char **argv) { int shmid; char *p_addr,*c_addr; if(argc!=2) { fprintf(stderr,"Usage:%s\n\a",argv[0]); exit(1); } /* 创建共享内存 */ if((shmid=shmget(IPC_PRIVATE,1024,PERM))==-1) { fprintf(stderr,"Create Share Memory Error:%s\n\a",strerror(errno)); exit(1); } /* 创建子进程 */ if(fork()) // 父进程写 { p_addr=shmat(shmid,0,0); memset(p_addr,'\0',1024); strncpy(p_addr,argv[1],1024); wait(NULL); // 释放资源,不关心终止状态 exit(0); } else // 子进程读 { sleep(1); // 暂停1秒 c_addr=shmat(shmid,0,0); printf("Client get %p\n",c_addr); exit(0); } }百度百科中对这些函数的介绍:
shmat(把共享内存区对象映射到调用进程的地址空间)
|
||
所需头文件
|
#include <sys/types.h>
#include <sys/shm.h>
|
|
函数说明
|
连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问
|
|
函数原型
|
void *shmat(int shmid, const void *shmaddr, int shmflg)
|
|
函数传入值
|
shmid |
共享内存标识符
|
shmaddr
|
指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置
|
|
shmflg
|
SHM_RDONLY:为只读模式,其他为读写模式
|
|
函数返回值
|
成功:附加好的共享内存地址
|
|
出错:-1,错误原因存于error中
|
||
附加说明
|
fork后子进程继承已连接的共享内存地址。exec后该子进程与已连接的共享内存地址自动脱离(detach)。进程结束后,已连接的共享内存地址会自动脱离(detach)
|
|
错误代码
|
EACCES:无权限以指定方式连接共享内存
EINVAL:无效的参数shmid或shmaddr
ENOMEM:核心内存不足
|
shmget(得到一个共享内存标识符或创建一个共享内存对象)
|
||
所需头文件
|
#include <sys/ipc.h>
#include <sys/shm.h>
|
|
函数说明
|
得到一个共享内存标识符或创建一个共享内存对象并返回共享内存标识符
|
|
函数原型
|
int shmget(key_t key, size_t size, int shmflg)
|
|
函数传入值
|
key
|
0(IPC_PRIVATE):会建立新共享内存对象
|
大于0的32位整数:视参数shmflg来确定操作。通常要求此值来源于ftok返回的IPC键值
|
||
size
|
大于0的整数:新建的共享内存大小,以字节为单位
|
|
0:只获取共享内存时指定为0
|
||
shmflg
|
0:取共享内存标识符,若不存在则函数会报错
|
|
IPC_CREAT:当shmflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符
|
||
IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的共享内存,则新建一个消息队列;如果存在这样的共享内存则报错
|
||
函数返回值
|
成功:返回共享内存的标识符
|
|
出错:-1,错误原因存于error中
|
||
附加说明
|
上述shmflg参数为模式标志参数,使用时需要与IPC对象存取权限(如0600)进行|运算来确定信号量集的存取权限
|
|
错误代码
|
EINVAL:参数size小于SHMMIN或大于SHMMAX
EEXIST:预建立key所指的共享内存,但已经存在
EIDRM:参数key所指的共享内存已经删除
ENOSPC:超过了系统允许建立的共享内存的最大值(SHMALL)
ENOENT:参数key所指的共享内存不存在,而参数shmflg未设IPC_CREAT位
EACCES:没有权限
ENOMEM:核心内存不足
|
shmdt(断开共享内存连接)
|
|
所需头文件
|
#include <sys/types.h>
#include <sys/shm.h>
|
函数说明
|
与shmat函数相反,是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存
|
函数原型
|
int shmdt(const void *shmaddr)
|
函数传入值
|
shmaddr:连接的共享内存的起始地址
|
函数返回值
|
成功:0
|
出错:-1,错误原因存于error中
|
|
附加说明
|
本函数调用并不删除所指定的共享内存区,而只是将先前用shmat函数连接(attach)好的共享内存脱离(detach)目前的进程
|
错误代码
|
EINVAL:无效的参数shmaddr
|
shmctl(共享内存管理)
|
||
所需头文件
|
#include <sys/types.h>
#include <sys/shm.h>
|
|
函数说明
|
完成对共享内存的控制
|
|
函数原型
|
int shmctl(int shmid, int cmd, struct shmid_ds *buf)
|
|
函数传入值
|
shmid
|
共享内存标识符
|
cmd
|
IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中
|
|
IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内
|
||
IPC_RMID:删除这片共享内存
|
||
buf
|
共享内存管理结构体。具体说明参见共享内存内核结构定义部分
|
|
函数返回值
|
成功:0
|
|
出错:-1,错误原因存于error中
|
||
错误代码
|
EACCESS:参数cmd为IPC_STAT,确无权限读取该共享内存
EFAULT:参数buf指向无效的内存地址
EIDRM:标识符为shmid的共享内存已被删除
EINVAL:无效的参数cmd或shmid
EPERM:参数cmd为IPC_SET或IPC_RMID,却无足够的权限执行
|
int open_queue(key_t keyval) { int qid; if ((qid = msgget(keyval, IPC_CREAT)) == -1) { return (-1); } return (qid); }七、发送消息
struct msgbuf { long mtype; //消息类型 > 0 char mtext[1]; //消息数据的首地址 };八、接收消息
功能:从 msqid 代表的消息队列中读取一个 msgtyp 类型的消息,并把消息存储在 msgp 指向的msgbuf结构中。在成功地读取了一条消息以后,队列中的这条消息将被删除。
int read_message(int qid, long type, struct mymsgbuf* qbuf) { int result,length; length = sizeof(struct mymsgbuf) - sizeof(long); if ((result = msgrcv(qid, qbuf, length, type, 0)) == -1) return (-1); return (result); }例:msg.c
#include <sys/types.h> #include <sys/msg.h> #include <unistd.h> struct msg_buf { int mtype; char data[255]; }; int main() { key_t key; int msgid; int ret; struct msg_buf msgbuf; key=ftok("/tmp/2",'a'); printf("key =[%x]\n",key); msgid=msgget(key,IPC_CREAT|0666); /*通过文件对应*/ if(msgid==-1) { printf("create error\n"); return -1; } msgbuf.mtype = getpid(); strcpy(msgbuf.data,"test haha"); ret=msgsnd(msgid,&msgbuf,sizeof(msgbuf.data),IPC_NOWAIT); if(ret==-1) { printf("send message err\n"); return -1; } memset(&msgbuf,0,sizeof(msgbuf)); ret=msgrcv(msgid,&msgbuf,sizeof(msgbuf.data),getpid(),IPC_NOWAIT); if(ret==-1) { printf("recv message err\n"); return -1; } printf("recv msg =[%s]\n",msgbuf.data); }
信号量(又名:信号灯)与其他进程间通信方式不大相同,主要用途是保护临界资源。进程可以根据它判定是否能够访问某些共享资源。除了用于访问控制外,还可用于进程同步。
二、分类
1,二值信号灯:信号灯的值只能取0或1,类似于互斥锁。但两者有不同:信号灯强调共享资源,只要共享资源可用,其他进程同样可以修改信号灯的值;互斥锁更强调进程,占用资源的进程使用完资源后,必须由进程本身来解锁。
2,计数信号灯:信号灯的值可以取任意非负值。
三、创建/打开
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg)
key : 键值,由 ftok 获得
nsems :指定打开或者新创建的 信号灯集 中将包含信号灯的数目
semflg :标识,同消息队列
四、操作
int semop(int semid, struct sembuf* sops, unsigned nsops)
功能:对信号量进行控制。
semid : 信号量集的ID
sops : 是一个操作数组,表明要进行什么操作
nsops : sops所指向的数组的元素个数。
struct sembuf { unsigned short sem_num; // semaphore index in array short sem_op; // semaphore operation short sem_flg; // operation flags }sem_num : 要操作的信号量在信号量集中的编号,第一个信号的编号是 0.