1、进程间通信
(1)、管道(pipe):半双工,亲愿关系间通信(父子进程或兄弟进程关系叫亲缘关系)。
(2)、有名管道(named pipe):半双工,容许无亲缘关系进程间的通信。
(3)、信号量(semophore):计数器,控制多个进程对共享资源的访问,进程间以及同一进程内不同线程间的同步手段。
(4)、消息队列(message queue):消息的链表,传递信息量大。
(5)、信号(signal):通知接收进程某个事件已经发生。
(6)、共享内存(shared memory):映射一段能被其他进程所访问的内存,由一个进程创建但多个进程可以访问。
(7)、套接字(socket):用于不同机器间的进程通信。
注:通信用的信号处理函数
(1)、write函数
1.功能
将数据写入已打开的文件内
2.相关函数
open,read,fcntl,close,lseek,sync,fsync,fwrite
3.表头文件
#include<unistd.h>
4.定义函数
ssize_t write (int fd,const void * buf,size_t count);
5.函数说明
write()会把参数buf所指的内存写入count个字节到参数fd所指的文件内。当然,文件读写位置也会随之移动。
6.返回值
如果顺利write()会返回实际写入的字节数。当有错误发生时则返回-1,错误代码存入errno中。
7.错误代码
EINTR 此调用被信号所中断。
EAGAIN 当使用不可阻断I/O 时(O_NONBLOCK),若无数据可读取则返回此值。
EBADF 参数fd非有效的文件描述词,或该文件已关闭。
(2)、read函数
1.功能
由已打开的文件读取数据
2.相关函数
readdir,write,fcntl,close,lseek,readlink,fread
3.表头文件
#include<unistd.h>
4.定义函数
ssize_t read(int fd,void * buf ,size_t count);
5.函数说明
read()会把参数fd所指的文件传送count个字节到buf指针所指的内存中。若参数count为0,则read()不会有作用并返回0。返回值为实际读取到的字节数,如果返回0,表示已到达文件尾或是无可读取的数据,此外文件读写位置会随读取到的字节移动。
6.附加说明
如果顺利read()会返回实际读到的字节数,最好能将返回值与参数count作比较,若返回的字节数比要求读取的字节数少,则有可能读到了文件尾、从管道(pipe)或终端机读取,或者是read()被信号中断了读取动作。当有错误发生时则返回-1,错误代码存入errno中,而文件读写位置则无法预期。
错误代码 EINTR 此调用被信号所中断。
EAGAIN 当使用不可阻断I/O时(O_NONBLOCK),若无数据可读取则返回此值。
EBADF 参数fd 非有效的文件描述词,或该文件已关闭。
(3)、pipe()
#include<unistd.h>
int pipe(int filedes[2]);
返回值:成功,返回0,否则返回-1。参数数组包含pipe使用的两个文件的描述符。fd[0]:读管道,fd[1]:写管道。
必须在fork()中调用pipe(),否则子进程不会继承文件描述符。两个进程不共享祖先进程,就不能使用pipe。但是可以使用命名管道。
2、管道编程
管道创建:
由描述符fd[0]、fd[1]来描述,fd[0]为管道读端,fd[1]管道写端。
用法:进程在使用fork创建子进程前先创建一个管道,用于父子进程通信,然后创建子进程,之后父进程关闭管道读端,子进程关闭管道写端。父进程向管道写数据,子进程向
管道读数据。
示例(转):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
void read_from_pipe(int fd)
{
char message[100];
read(fd, message, 100);
printf("read from pipe:%s\n", message);
}
void write_to_pipe(int fd)
{
char *message = "hello, pipe!";
write(fd, message, strlen(message)+1);
}
int main()
{
int fd[2];
pid_t pid;
int stat_val;
if(pipe(fd))
{
printf("create pipe failed!\n");
exit(1);
}
pid = fork();
switch(pid){
case 0:
close(fd[0]);
read_from_pipe(fd[0]);
exit(0);
case -1:
printf("fork error!\n");
exit(1);
default:
close(fd[0]);
write_to_pipe(fd[1]);
wait(&stat_val);
exit(0);
}
return 0;
}
注:例子中子进程可以直接共享父进程的文件描述符,但如果子进程调用exec函数执行另一个应用程序时,就不能在共享了。新执行的程序从标准输入获取数据时实际上是从父进程中获取输入数据。dup和dup2函数提供了复制文件描述符的功能。dup和dup2函数调用成功时均返回一个oldfd文件描述符副本,失败返回-1.
比较:
/****dup***/
pid = fork();
if(pid == 0)
{
close(1);
dup(fd[1]);
execve("exam", arge, enen);
...
}
/****dup2***/
pid = fork();
if(pid == 0)
{
dup2(1, fd[1]);
execve("exam", arge, enen);
...
}
由对比发现dup2系统调用将close操作和文件描述符拷贝操作集成在同一个函数里,它保证了操作的原子性。3、有名管道
named pipe 或 FIFO,FIFO提供了一个路径名与之关联,以FIFO的文件形式存储与文件系统中。(可用于不存在亲缘关系的进程中)
两创建方法:1是在shell下利用mknod或mkfifo命令交互的建立一个有名管道,2是使用系统函数建立。
示例:
umask(0);
if(mkfifo("/tmp/fifo", S_FIFO|0666, 0) == -1)
{
perror("mkfifo");
exit(1);
}
注:umask(0):
linux中的 umask 函数主要用于:在创建新文件或目录时屏蔽掉新文件或目录不应有的访问允许权限。文件的访问允许权限共有9种,分别是:r w x r w x r w x(它们分别代表:用户读 用户写 用户执行 组读 组写 组执行 其它读 其它写 其它执行)。其实这个函数的作用,就是设置允许当前进程创建文件或者目录最大可操作的权限,比如这里设置为0,它的意思就是0取反再创建文件时权限相与,也就是:(~0) & mode 等于八进制的值0777 & mode了,这样就是给后面的代码调用函数mkdir给出最大的权限,避免了创建目录或文件的权限不确定性。
S_FIFO|0666:
指明创建一个有名管道且存取权限为0666,即创建者、与创建者同组的用户、其他用户对该有名管道的访问权都是可读可写。
有名管道是硬盘上的文件,必须用open()先将其打开。(管道存在于内存中的特殊文件)
4、消息队列
1)消息队列的基本结构:
存放在内核中的消息链表,每个消息队列由消息队列标识符标识。包含三个结构体:消息缓冲结构体,msqid_ds内核数据结构(保存消息队列当前的状态信息),ipc_perm内核数据结构(保存键值以及用户ID、组ID等)。
2)消息队列的创建:
消息队列随内核存在而存在,每个消息队列在系统范围内对应唯一键值。提供消息队列的键值就可以获得一个消息队列的描述符,该键值由ftok函数返回。
函数原型:key_t ftok(const char *pathname, int proj_id);//pathname在系统存在且进程有权访问,参数proj_id取值范围1-255.
头文件:#include <sys/ipc.h>
返回值:成功返回一个键值,失败返回-1。
ftok函数返回的键值可以提供给函数msgget
函数原型:int msgget(key_t key, int msgflg)//key是ftok函数的返回值,msgflg是标志参数:IPC_CREATE, IPC_EXCL
头文件:#include <sys/msg.h>
返回值:成功返回一个消息队列的描述符,否则返回-1.
3)写消息队列:
函数msgsnd用于向消息队列发送(写)数据。
函数原型:int msgsnd(int msqid, struct msgbuf *msgp, size_t msgsz, int msgflg)
msqid:消息队列描述符
msgp:指向要发送的消息
msgsz:要发送的消息大小,不包含消息类型占用的4个字节。方法:msglen = sizeof(消息缓冲数据结构)-4
magflg:操作标志位。设置为0或IPC_NOWAIT。为0,当消息队列满时msgsnd将会阻塞,直到消息可以写进消息队列;为IPC_NOWAIT,当消息队列满时,函数不等待立即返回。
头文件:#include <sys/msg.h>
返回值:成功返回0,否则返回-1.
4)读消息队列:
读取消息的系统调用为msgrcv()
函数原型:int msgrcv(int msqid, struct msgbuf *msgp, size_t msgsz, long int msgtyp, int msgfly)
msqid:消息队列描述符
msgp:读取消息存储到msgp指向的消息结构中
msgsz:消息缓冲区的大小
msgtyp:请求读取的消息类型
msgflg:操作标志位。
函数返回值:成功返回读取消息的实际字节数,否则返回-1.
5、信号量(相关函数在头文件<sys/sem.h>中)
信号量是一个计数器,常处理进程或线程同步问题,特别是对临界资源访问的同步。只有得到信号量的进程才能执行临界区代码,当获取不到信号量时,进程会进入休眠等待状态。
1)信号量的创建:
使用系统函数semget创建或打开信号集。
函数原型:int semget(key_t key, int nsems, int semflg);
key:由ftok()得到这个键值
nsems:指明要创建的信号集包含的信号个数,如果只是打开信号集,设置为0即可。
semflg:操作标志,IPC_CREATE,IPC_EXCL
2)信号量的操作:
信号量的值仅能由PV操作来改变。PV操作由semop实现
函数原型:int semop(int semid, struct sembuf *sops, size_t nsops);
semid:信号集的标识符
sops:指向进行操作的结构体数组首地址
nsops:指出将要进行操作的信号的个数
返回值:成功返回0,否则返回-1
3)对信号量进行操作的数据结构:
struct sembuf{
ushort sem_num;//信号在信号集中的索引
short sem_op;//操作类型,有三种取值类型>0 ,<0, =0
short sem_flg;//操作标志
}
操作举例:
/*p操作*/
int sem_p(int semid, int index)
{
....
struct sembuf buf = {0, -1, IPC_NOWAIT};//参数变化
buf.sem_num = index;
if(semop(semid, &buf, 1) == -1)
{perror("error"); return -1} ...
}
/*V操作*/
int sem_v(int semid, int index)
{
....
struct sembuf buf = {0, 1, IPC_NOWAIT};//参数变化
buf.sem_num = index;
if(semop(semid, &buf, 1) == -1)
{perror("error"); return -1} ...
}
4)信号量的控制如删除操作等
操作函数:int semctl(int semid, int semnum, int cmd, ...)
semid:信号量的标识符
semnum:标识的一个特定信号(详参资料)
cmd:指明控制操作的类型(详参资料)
6、共享内存
分配一块能被其他进程访问的内存。
1)共享内存的创建
创建或者访问一个已存在的共享内存函数shmget()
函数原型:shmget(key_t key, size_t size, int shmflg)
key:由ftok函数得到的键值
size:以字节为单位,指定内存的大小
shmflg:操作标志位,一组宏
返回值:成功返回共享内存的ID,失败返回-1.
2)共享内存的操作
在使用共享内存前,通过shmat函数将其附加到进程的地址空间,进程与共享内存就建立了连接。
函数原型:void* shmat(int shmid, const void *shmaddr, int shmflg)
shmid:是函数shmget的返回值(共享内存的id)
shmaddr:共享内存的附加点,通常设为NULL,表示内核选择一个空闲内存区
shmflg:存取权限标志。
返回值:成功返回连接的实际地址,失败为-1.
当进程结束使用共享内存时通过函数shmdt函数断开与共享内存的连接。
函数原型:int shmdt(const void* shmaddr)
shmaddr:为函数shmat函数的返回值
返回值:成功为0,失败为-1
3)共享内存的控制
函数原型:int shmctl(int shmid, int cmd, struct shmid_ds *buf)
shmid:共享内存区的标识符
cmd:操作标志位
buf:指向shmid_ds结构体的指针