文章目录
1.pipe
管道允许一个进程和另一个与它有共同祖先的进程之间进行通信,即只能用于在具有血缘关系的进程之间通信。示例程序:
#include <unistd.h>
#include <string.h>
#include <stdio.h>
int main(){
int fd[2];
int res = pipe(fd);
if(res==-1)
perror("pipe error");
pid_t pid = fork();
if(pid<0){
perror("fork error");
}else if(pid==0){
close(fd[1]);
char buf[BUFSIZ];
ssize_t n = read(fd[0],buf,sizeof(buf));
printf("read %ld bytes,%s\n",n,buf);
}else{
close(fd[0]);
char buf[BUFSIZ];
sprintf(buf,"Hello world!");
ssize_t n = write(fd[1],buf,strlen(buf));
printf("send %ld bytes,process %d\n",n,getpid());
}
return 0;
}
2.fifo:命名管道
类似于管道,但是它可以用于任何两个进程之间的通信,命名管道在文件系统中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建,示例:
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#define fifo "/tmp/fifo"
int main(){
unlink(fifo);
int res = mkfifo(fifo,0777);
if(res==-1)
perror("mkfifo error");
pid_t pid = fork();
if(pid<0){
perror("fork error");
}else if(pid==0){
int rfd = open(fifo,O_RDONLY);
char buf[BUFSIZ];
ssize_t n = read(rfd,buf,sizeof(buf));
printf("read %ld bytes,%s\n",n,buf);
}else{
int wfd = open(fifo,O_WRONLY);
char buf[BUFSIZ];
sprintf(buf,"Hello world!");
ssize_t n = write(wfd,buf,strlen(buf));
printf("send %ld bytes,process %d\n",n,getpid());
}
return 0;
}
3.signal:信号
信号是比较复杂的通信方式,用于通知接收进程有某种事情发生,除了用于进程间通信外,进程还可以发送信号给进程本身;Linux除了支持UNIX早期信号语义函数signal外,还支持语义符合POSIX.1标准的信号函数sigaction。此处需要掌握signal与signaction的区别,signal直接指定了信号发生时的回调函数,而后者还可以通过参数设置信号屏蔽集,可以通过函数sigemptyset/sigaddset等来清空和增加需要屏蔽的信号.
signal:
int main(){
signal(SIGINT,SIG_IGN);
while(1);
return 0;
}
signaction:
int main(){
struct sigaction act;
act.sa_handler = SIG_IGN;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask,SIGTSTP);//SIGQUIT为^z产生
act.sa_flags = 0;
sigaction(SIGINT,&act,0);//act.sa_mask仅表示临时阻塞
while(1);
return 0;
}
4. 共享内存:shared memory
内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享内存映射到自己的进程地址空间来实现它,相关函数如下:
//根据pathname指定的文件或目录名称,以及proj_id参数指定的数字,ftok函数为IPC对象生成一个唯一性的键值。
int shmget(key_t key,size_t size,int shmflg)//用于创建共享内存
/*key:这个共享内存段的名字,常使用ftok函数获取,key_t ftok( const char * fname, int id )
*size:共享内存的大小
*shmflg:由九个权限标志构成,其用法跟创建文件时使用的mode模式标志一样。
*返回值:成功返回一个非负整数,即该共享内存段的标志码;失败返回-1
*/
void* shmat (int shmid, const void* shmaddr, int shmflg)//将共享内存连接到进程地址空间某处
/*shmid:共享内存标识,shmget函数得到
*shmaddr:指定连接的地址,一般取NULL由内核自己选取
*shmflg:它可能取SHM_RDONLY表示只读,其他为读写,可设置为0表示读写
*返回值:成功返回一个指针,指向共享内存的第一节;失败返回-1
*/
int shmdt(const void *shmaddr)//将共享内存段与当前进程脱离
/*shmaddr: 由shmat所返回的指针
*返回值:成功返回0;失败返回-1
*/
int shmctl (int shmid, int cmd, struct shmid_ds* buf)//⽤于控制共享内存
/*shmid:由shmget返回的共享内存标识码
*cmd:将要采取的动作(有三个可取值)
*buf:指向⼀个保存着共享内存的模式状态和访问权限的数据结构,一般取NULL
*返回值:成功返回0;失败返回-1
*cmd:IPC_STAT:将shmid_ds结构中的数据设置为共享内存当前的关联值
* IPC_SET:在进程权限足够的条件下,将共享内存的当前关联值设置为shmid_ds数据结构中给出的值
* IPC_RMID:删除共享内存
*/
示例程序:
#define PROJ_ID 0x7777
#define shmfile "/tmp/shfile"
int main()
{
key_t key = ftok(shmfile,PROJ_ID);
int shmid = shmget(key,BUFSIZ,IPC_CREAT|IPC_EXCL|0777);//IPC_CREAT创建新的shm
char *des = shmat(shmid,NULL,0);
sleep(2);
int i=0;
while(i<26)
{
i++;
printf("read bytes,%s\n",des);
sleep(1);
}
shmdt(des);
shmctl(shmid,IPC_RMID,0);
return 0;
}
5.消息队列(message queue)
消息队列是消息的连接表,包括POSIX消息对和System V 消息队列.有足够权限的进程 可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能传输无格式字节流以及缓冲区大小受限等缺点。
struct msg_buffer{ //消息结构
long mtype;
char mtext[1024];
};
- 使用ftok函数获取一个systemV的key值,然后依据key值调用msgget函数获取消息队列id.
key_t key = ftok("/tmp/message",1024);
int messid = msgget(key,IPC_CREAT|0777);
- 使用ipcs -q 可以查看创建的消息队列
--------- 消息队列 -----------
键 msqid 拥有者 权限 已用字节数 消息
0x00000045 32769 centos 777 0 0
- 使用msgsnd函数向消息队列发送消息
int msgsnd(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
/*msgid是由msgget函数返回的消息队列标识符
*msg_ptr是一个指向准备发送消息的指针,但是消息的数据结构却有一定的要求,
指针msg_ptr所指向的消息结构一定要是以一个长整型成员变量开始的结构体,接收函数
将用这个成员来确定消息的类型。如上面定义的struct msg_buffer结构
*msg_sz 是msg_ptr指向的消息的长度,注意是消息的长度,而不是整个结构体的长度,
也就是说msg_sz是不包括长整型消息类型成员变量的长度。
*msgflg 用于控制当前消息队列满或队列消息到达系统范围的限制时将要发生的事情,如
IPC_NOWAIT表示不阻塞等待
*return :调用成功,消息数据的一分副本将被放到消息队列中,并返回0,失败时返回-1
*/
- 使用msgrcv函数读取消息队列里面的消息
int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);
/*msgid, msg_ptr, msg_st 的作用与函数msgsnd()函数的一样。
*msgtype 可以实现一种简单的接收优先级。如果msgtype为0,就获取队列中的第一个消息。
如果它的值大于零,将获取具有相同消息类型的第一个信息。如果它小于零,就获取类型
等于或小于msgtype的绝对值的第一个消息。
*msgflg的作用与函数msgsnd()函数的一样。
*调用成功时,该函数返回放到接收缓存区中的字节数,消息被复制到由msg_ptr指向的
用户分配的缓存区中,然后删除消息队列中的对应消息。失败时返回-1。
*/
- 使用msgctl可以删除或者操作消息队列,也可在命令行使用ipcrm删除消息队列
int msgctl(int msgid, int command, struct msgid_ds *buf);
/*掌握当command等于IPC_RMID表示删除消息队列,struct msgid_ds可取为NULL即可。*/
6.信号量(semaphore)
信号量主要作为进程间以及同进程不同线程之间的同步手段。主要是为了保护共享资源,在进程间通信时常常和共享内存结合在一起使用,下面主要介绍systemV的信号量机制。
- 信号量定义P操作,用于申请资源操作
- 信号量定义V操作,用于释放资源操作
下面是systemV的信号量函数:
(1)semget函数
int semget(key_t key, int num_sems, int sem_flags);
/*arg1:来源同shmget,msgget
*arg2:num_sems指定需要的信号量数目,它的值几乎总是1
*arg3:sem_flags是一组标志,当想要当信号量不存在创建一个新的信号量时,可以和值IPC_CREAT
做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生
错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。
*retval:成功返回一个相应信号标识符(非零),失败返回-1
*/
(2)semctl()函数
int semctl(int sem_id, int sem_num, int command, ...);
如果有第四个参数,它通常是一个union semum结构,定义如下:
union semun {
int val;
struct semid_ds *buf;
unsigned short *arry;
};
前两个参数与前面一个函数中的一样,command通常是下面两个值中的其中一个:
SETVAL:用来把信号量初始化为一个已知的值,这个值通过union semun
中的val成员设置,其作用是在信号量第一次使用前对它进行设置。
IPC_RMID:用于删除一个已经无需继续使用的信号量标识符
(3)semop()函数
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
sem_id是由semget()返回的信号量标识符,sembuf结构的定义如下:
struct sembuf{
short sem_num; // 除非使用一组信号量,否则它为0
short sem_op; // 信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
// 一个是+1,即V(发送信号)操作。
short sem_flg; // 通常为SEM_UNDO,使操作系统跟踪信号,
// 并在进程没有释放该信号量而终止时,操作系统释放信号量
};
当 sem_flg 被设置为IPC_NOWAIT时就是没有资源也不等待,否则将挂起等待。sem_op>0表示归还资源将它加到信号量的值上。如果有进程正在休眠等待此信号量,则唤醒他们。
7.套接字(Socket)
它是更为通用的进程间通信机制,可用于不同机器之间的进程间通信。最熟悉了,不言说了。