进程间通信及同步的方式有:pipe、fifo、共享内存、信号量、消息队列、socket通信等
1:管道
两个或多个进程间通过管道,可以互相传递信息,利用read和write系统调用函数来进行读写操作。
int pipe(int filedes[2]);
filedes是一个有两个成员的整形数组。
(1)filedes[0]将用来从管道读取数据
(2)filedes[1]用来向管道写入数据。
如下图所示:
示例代码:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main() {
int data_processed;
int file_pipes[2];
const char some_data[] = “123”;
char buffer[512];
memset(buffer, ‘\0’, sizeof(buffer));
if (pipe(file_pipes) == 0)
{
data_processed=write(file_pipes[1],some_data,strlen(some_data));
printf(“Wrote %d bytes\n”, data_processed);
data_processed = read(file_pipes[0], buffer, BUFSIZ);
printf(“Read %d bytes: %s\n”, data_processed, buffer);
exit(EXIT_SUCCESS);
}
exit(EXIT_FAILURE);
}
2:命名管道FIFO
(1)提供进程间数据交换的一种机制
(2)与pipe不同的是,命名管道不需要程序由一个共同的祖先进程启动
(3)命名管道是一种特殊类型的文件,因为是文件,具备了和文件相同的特点,有文件名、所有者,访问权限等,但行为和管道相同
2.1:命令行方式创建FIFO管道
$ mknod filename p
$ mkfifo filename
2.2:程序中创建FIFO管道
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *filename, mode_t mode);
int mknod(const char *filename, mode_t mode | S_IFIFO, (dev_t)0);
参数filename为指定管道文件的名字
参数mode给出了FIFO的访问权限
例程:创建命名管道FIFO
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
int res = mkfifo(“/tmp/my_fifo”, 0777);
if (res == 0)
printf(“FIFO created\n”);
exit(EXIT_SUCCESS);
}
2.3:打开FIFO文件
与通过pipe调用创建管道不同,FIFO是以命名文件的形式存在,而不是打开的文件描述符,所以对它进行读写操作之前必须先打开它。
FIFO也用open和close函数打开和关闭,但多了额外的功能
例:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#define FIFO_NAME “/tmp/my_fifo”
int main() {
int res;
int open_mode = 0;
open_mode = O_RDONLY | O_NONBLOCK;
res=open(FIFO_NAME, open_mode);
if (res == -1)
{
perror(“Failed tp open FIFO file”);
exit(1);
}
printf(“FIFO file is opened\n”);
clsoe(res);
return 0;
}
注:注意:打开管道文件,可以选择阻塞与非阻塞两种方式。
例程:FIFO读、写程序
读:
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#define FIFO_NAME "/tmp/my_fifo“
int main()
{
char buf[100];
int fd;
int nread;
fd = open(FIFO_NAME, O_RDONLY ,0);
if (fd == -1)
{
perror("open");
exit(1);
}
memset(buf,0,sizeof(buf));
if ((nread = read(fd, buf, 100))==-1)
{
perror("read");
exit(1);
}
printf("read %s from FIFO\n",buf);
return 0;
}
写:
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#define FIFO_NAME "/tmp/my_fifo“
int main()
{
int fd;
int nwrite;
char buf[100];
fd = open(FIFO_NAME,O_WRONLY ,0);
if (fd == -1)
{
perror("Failed to open FIFO file");
}
printf("FIFO file is opened\n");
strcpy(buf,"helloworld");
if ((nwrite=write(fd,buf,100))==-1)
{
perror("write");
exit(1);
}
close(fd);
return 0;
}
3:信号量
(1)确保程序对某个特定的资源具有独占式的访问的机制
(2)信号量是特殊的变量,只能取正整数并且只允许有两种操作:等待P和信号V
3.1:IPC机制关键值
#include <sys/ipc.h>
key_t ftok(const char *path, int id);
函数的作用是以path相关的信息为基础返回一个关键值,利用此关键值实现IPC同步和通信机制,也可使用IPC_PRIVATE作为参数由系统自动生成。
ftok与IPC_PRIVATE的区别:
ftok借助文件实现功能,常在不同的进程中使用,尤其是两个进程不是父子进程时,但因为是借助文件实现,一旦文件被删除后重新创建,获得的key就会发生变化,影响两个进程同步及通信机制的实现
IPC_PRIVATE由系统自动生成键值,常用于父子进程中,如要运行非父子进程关系的两个进程间,需要传值才能让另外的进程获得IPC_PRIVATE的值
3.2:semget函数:创建信号量集
#include <sys/sem.h>
int semget(key_t key, int nsems, int sem_flags);
参数key:整数,相关进程可以通过它访问一个信号量集
参数nsems:指定一个信号量集里的信号量数量。它几乎总是取值1
参数sem_flags:是一组标志,与open函数的标志相似
3.3:semop函数:实际执行各种信号量操作
#include <sys/sem.h>
int semop(int semid, struct sembuf * op_array, size_t num_ops);
参数semid:为semget函数返回的信号量集标识符
参数op_array:对应一个或多个信号量的sembuf结构体操作
参数num_ops:同时对几个信号量进行操作
sembuf结构体数据成员说明
struct sembuf
{
short sem_num;
short sem_op;
short sem_flg;
}
sem_num: 对信号量集中的哪个信号量进行操作,从0开始表示
sem_op: 操作数设置,通常是+1或-1操作
sem_flg: 功能设置,IPC_NOWAIT与SEM_UNDO
3.4:semctl 函数:在一组信号量上做各种控制操作,诸如信号量集合的初始化、删除和状态查询等
#include <sys/sem.h>
int semctl(int semid, int sem_num, int command, union semun ctl_arg);
参数semid:为semget的返回值
参数sem_num:指定特定的信号量
参数ctl_arg 是一个联合体,对应三种不同功能的semctl调用
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
}
参数command:有三种功能:
(1)标准的IPC函数:
IPC_STAT : 把状态信息放入ctl_arg.stat中
IPC_SET : 用ctl_arg.stat中的值设置所有权/许可权
IPC_RMID : 从系统中删除信号量集合
(2)单信号量操作:
GETVAL : 返回信号量的值
SETVAL : 把信号量的值写入ctl_arg.val中
GETPID : 返回sempid值
GETNCNT : 返回semncnt
GETZCNT : 返回semzcnt
(3)全信号量操作:
GETALL : 把所有信号量的semvals值写入ctl_arg.array
SETALL : 用ctl_arg.array中的值设置所有信号量的semvals
3.5例子:
(1)创建信号量
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int semid;
int nsems = 1;
int flags = 0666;
struct sembuf buf;
semid = semget(IPC_PRIVATE, nsems, flags);
if (semid < 0) {
perror(“semget”);
exit(EXIT_FAILURE);
}
printf(“semphore created: %d\n”, semid);
buf.sem_num = 0;
buf.sem_op = 1;
buf.sem_flg = IPC_NOWAIT;
if ((semop(semid, &buf, nsems)) < 0) {
perror(“semop”);
exit(EXIT_FAILURE);
}
system(“ipcs –s”);
exit(EXIT_SUCCESS);
}
(2)删除信号量
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int semid;
if (argc !=2)
{
puts(“USAGE:sctl <semaphore id>”);
exit(EXIT_FAILURE);
}
semid = atoi(argv[1]);
if((semctl(semid, 0, IPC_RMID)) < 0)
{
perror(“semctl IPC_RMID”);
exit(EXIT_FAILURE);
}
else
{
puts(“semaphore removed”);
system(“ipcs –s”);
}
exit(EXIT_SUCCESS);
}
(3)进程同步操作
#include <sys/types.h>
#include <linux/sem.h>
#include <unistd.h>
int pid,semid;
struct sembuf P,V;
union semun arg;
int main(void)
{
arg.val = 1;
semid = semget(IPC_PRIVATE,1,0666|IPC_CREAT);
semctl(semid, 0, SETVAL, arg);
P.sem_num = 0;
P.sem_op = -1;
P.sem_flg = SEM_UNDO;
V.sem_num = 0;
V.sem_op = 1;
V.sem_flg = SEM_UNDO;
if ((pid = fork()) == -1)
{
perror(“fork”);
exit(1);
}
else if (pid == 0)
{
semop(semid,&P,1);
system(“echo –n ‘a’ >> ./printer);
system(“echo –n ‘b’ >> ./printer);
system(“echo ‘c’ >> ./printer);
semop(semid,&V,1);
}
else
{
semop(semid,&P,1);
system(“echo –n ‘1’ >> ./printer);
system(‘echo –n ‘2’ >> ./printer);
system(‘echo ‘3’ >> ./printer);
semop(semid,&V,1);
}
semctl(semid, IPC_RMID,0);
exit(0);
}
4:消息队列
4.1:msgget函数:创建和访问消息队列
#include<sys/msg.h>
int msgget(key_t key, int permflags);
参数key:为标识好的消息队列
参数permflags:有两个值可选择:
IPC_CREAT : 创建新的队列
IPC_EXCL : 与IPC_CREAT合用,如要创建的队列已存在,返回值为-1
例:创建消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
int main (int argc, char *argv[])
{
int qid;
key_t key;
key = 123;
if ((qid = msgget(key, IPC_CREAT | 0666)) < 0)
{
perror(“msgget:create”);
exit(EXIT_FAILURE);
}
printf(“created queue id = %d\n”, qid);
if ((qid == msgget(key, 0)) < 0) {
perror(“msgget:open”);
exit(EXIT_FAILURE);
}
printf(“opened queue id = %d\n”, qid);
exit(EXIT_SUCCESS);
}
4.2:
msgsnd函数:功能为向消息队列添加信息
msgrcv函数:功能为从队列中读取消息
#include <sys/msg.h>
int msgsnd(int mqid, const void * message, size_t size, int flags);
int msgrcv(int mqid, void * message, size_t size, long msg_type,int flags);
参数mqid:由msgget返回获得的
参数message:由用户自定义的结构
参数flags:只有一个有意义的值IPC_NOWAIT,当消息无法返回,调用立即返回,返回值为-1
参数msg_type:决定实际将读到的是哪条消息
4.3:msgctl函数:功能为使进程获取有关消息队列的状态信息、改变与队列有关的限制或从系统删除一个队列
int msgctl(int mqid, int command, struct msqid_ds *msq_stat);
参数mqid:有效的队列标识符
参数command:有三个值
IPC_STAT : 告诉系统把状态信息存入msq_stat中
IPC_SET : 用于通过msq_stat中存放的信息为消息队列设置控制变量的值
IPC_RMID : 从系统中删除消息队列
参数msq_stat:指向msqid_ds结构的指针
例程:发送与接收
#include <sys/types.h>
#include <linux/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
int qid, len;
struct msgbuf sndmsg;
struct msgbuf rcvmsg;
if ((qid = msgget(IPC_PRIVATE, IPC_CREAT|0666)) == -1)
{
perror(“msgget”);
exit(1);
}
sprintf(sndmsg.mtext, “hello”);
sndmsg.mtype = 1;
len = strlen(sndmsg.mtext);
if ((msgsnd(qid, &sndmsg, len, 0)) < 0)
{
perror(“msgsnd”);
exit(1);
}
puts(“message posted”);
sleep(1);
if ((msgrcv(qid, &rcvmsg, len, 0, 0)) < 0)
{
perror(“msgrcv”);
exit(1);
}
puts(“message received”);
rcvmsg.mtext[5]=‘\0\;
printf(“This message is %s\n”,rcvmsg.mtext);
msgctl(qid, IPC_RMID, NULL);
exit(0);
}
5:共享内存
(1)共享存储操作使得两个或两个以上的进程可以共用一段物理内存
(2)一般情况下,两个进程的数据区是完全独立的,通常它在所有的IPC机制中是最有效的
5.1:shmget函数:创建一段内存用于进程共享数据
int shmget(key_t key, size_t size, int permflags);
参数key:内存段的关键值
参数size:内存段所需最小字节数
参数permflags:给出了内存段的访问权限
例程:创建共享内存
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#define BUFSZ 4096
int main(void)
{
int shmid;
if((shmid = shget(IPC_PRIVATE, BUFSZ,0666)) < 0)
{
perror(“shmget”);
exit(EXIT_FAILURE);
}
printf(“segment created: %d\n”, shmid);
system(“ipcs –m”);
exit(EXIT_SUCCESS);
}
5.2:shmat函数:功能为将标识的内存段挂接到调用进程的有效地址上
void *shmat(int shmid, const void *daddr, int shmflags);
参数shmid:表示的共享内存段
参数daddr:控制调用选择的地址
参数shmflags有2个值:
SHM_RDONLY: 使内存段只读挂接
SHM_RND : 使挂接的地址可以在daddr的一页范围内变动
如不设置,shmat将精确使用daddr的值
5.3:shmdt函数:取消共享内存的挂接
int shmdt(char *shmadr);
参数shmadr:取消挂接的共享内存
5.4:shmctl函数:功能为对共享内存进行控制管理
int shmctl(int shmid, int command, struct shmid_ds * shm_stat);
功能使用与msgctl函数类似,其中command参数可以取以下参数:
IPC_STAT 把shmid_ds结构中的数据设置为共享内存当前关联值
IPC_SET 进程有足够权限,就设置成shmid_ds中给出的值
IPC_RMID 删除共享内存段
struct shmid_ds
{
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
}
5.5:例:共享内存
#include <stdio.h>
#include <sys/shm.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
int main()
{
int shmid;
char *shmbuf;
pid_t pid;
key_t key;
shmid = shmget(IPC_PRIVATE, 100, IPC_CREAT|0777);
if (shmid == -1)
{
perror("shmid");
exit(1);
}
shmbuf = shmat(shmid, NULL, 0);
if (shmbuf == (void*) -1)
{
perror("shmat");
exit(1);
}
pid=fork();
if(pid == -1)
{
perror("fork");
exit(1);
}
if(pid == 0)
{
strcpy(shmbuf,"this is child");
}
else
{
printf("%s\n",shmbuf);
wait(NULL);
shmdt(shmid);
shmctl(shmid, IPC_RMID, NULL);
}
exit(0);
}
_