进程间通信(IPC)

 
共享内存、消息队列和信号量集等都是经典的UNIX进程间通讯(IPC)机制。
这些机制允许不相关的进行通过一种合理有效的途径来交换信息,这些机制用键(key)来标识、创建或访问相应的实体。
创建这些实体的进程寿命结束之后,这些实体仍然可能存在于系统之中,POSIX:XSI也提供了列举和删除这些实体的命令。
目标:
学习经典的进程间通信
研究同步的共享内存实验
研究信号量的实现
为进程间日志使用消息队列
理解持久性的重要性
 
1 .POSIX 进程间通信
POSIX进程间通信起源于UNIX System V进程间通信。IPC为同一个系统中的进程提供了共享消息的机制。(SOCKET为不同系统的进程提供了消息共享的机制)
POSXI进程间通信函数:
机 制
POSIX函数
含义
消息队列
msgctl
控制
 
msgget
创建或访问
 
msgrcv
接收消息
 
msgsnd
发送消息
信号量
semctl
控制
 
semget
创建或访问
 
semop
执行操作(等待或发送)
共享内存
shmat
将内存附加到进程中去
 
shmctl
控制
 
shmdt
将内存从进程中分离
 
shmget
创建并初始化或访问
1.1IPC 对象的标识和访问
表示符:
POSIX XSI 用一个唯一的整数来标识每个 IPC 对象,这个整数大于或等于0,对象的获取于open函数返回表示文件描述符的方式类似。
每个IPC对象类型中的整数表示符都是唯一的,但是两种不同类型的对象表示符可以相同。
这些标识符分别与sys/msh.h、sys/sem.h和sys/shm.h中定义的额外数据结构相关。
键(key):
创建或访问一个 IPC 对象时,必须指定一个键来说明要创建或访问的特定对象
a.由系统来选择一个键(IPC——PRIVATE);
b.直接选择一个键;
c.通过调用ftok请求系统从指定的路径生成一个键;
 #include <sys/ipc.h>
key_t ftok(const char *path, int id);
如果成功返回一个键,不成公返回(key_t)-1并设置errno,必须检测错误码:
EACCES            对一个path组件的搜索权限别否定
ELOOP             在解析path是存在循环
ENAMETOOLONG path的长度超出了PATH_MAX,或者一个路径名组件的长度超出了NAME_MAX
ENOENT            path的一个组件部石文件或者为空
ENOTDIR           path前缀的一个组件不是目录
功能:
函数允许独立的进程根据一个已知的路径名导出相同的键。对应于路径名的文件必须存在,并且必须能够别访问。paht和id的组合唯一地标识了IPC对象,参数id允许几个相同类型的IPC对象从一个路径名中生成键值。
例:从文件名/tmp/trouble.c中导出键
if ((thekey = ftok("tmp/trouble.c", 1)) == (key_t) -1)
 perror ("Failet to derive key from /tmp/trouble.c");
1.2 从命令解释程序中访问POSIX:XSI IPC 资源
这是信号量没有的一项很方便的特性。
显示命令:
ipcs [-qms] [-a | -bcopt]
-q -m -s限制对消息队列、共享内存、信号量等类型的显示
-a显示所有可用信息的长格式
-bcopt说明了可用信息的哪些部分是需要答应的
例:显示郁当前系统系统中分配的信号量有关的所有可用信息
ipcs -s -a
删除命令:
ipcrm [-q msgid | -Q msgkey | -s semid | -S semkey | -m shmid | -M shmkey] …
小写的-q -s -m用于用于对象ID指定的IPC类型
大写使用初始的创建键来删除IPC对象
 
2 .POSIX :XSI 信号量集
POSIX:XSI信号量有一个信号量元素数组组成,为区别POSIX:SEM信号量称其为信号量集(semaphore set)。
每个信号量元素中至少包含下列的信息:
a.一个表示信号量元素值得非负整数(semval)
b.最后一个操纵信号量元素的进程ID(sempid)
c.等待信号量元素值增加的进程的数量(semncnt)
d.等待信号量元素值变为零的进程的数量(semzcnt)
信号量集主要的数据结构是semid_ds,定义在sys/sem.h中,有下列成员:
struct      ipc_perm sem_perm;        //操作权限结构
unsigned short       sem_nsems;       //集合中信号量的个数
time_t     sem_otime;                  //最后一次semop的时间
tiem_t     sem_ctime;                  //最后一次semctl的时间
每个信号量元素都有两个与之相关的队列:一个等待信号量值变为0的进程队列和一个等待信号量值增加的进程队列,信号量元素操作允许进程阻塞。
2.1 信号量集的创建
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflag);
成功返回一个对应于信号量集标识符的非负整数,不成功返回-1并设置errno,错误码:
EACCES    存在key的信号量,但没有授予权限
EEXIST     存在key的信号量,但是
           ( (semflg & IPC_CREATE) && (semflg & IPC_EXCL) ) != 0
EINVAL     nsems <= 0或者大于系统的限制,或者nsems与信号量集的大小不符
ENOENT    不存在key的信号量,而且(semflg & IPC_CTEATE) == 0
ENOSPC    要超出系统范围内对信号量的限制了
功能:
函数返回与参数 key 相关的信号量集标识符。
如果键值为IPC_PRIVATE,或者semflg&IPC_CREAT非零且没有信号量集或标识符关联于key,那么函数就创建标识符及与之相关的信号量集。
参数nsems指定了集合中信号量元素的个数,可用0到nsems-1的整数来引用信号量集合中的单个信号量元素。
参数semflg指定信号量集的优先级,权限的设置与文件权限设置相同,并可以通过semclt来修改权限值,在使用信号量元素之前,应该用semctl对其进行初始化。
注意:
函数如果试图创建一个已经存在的信号量集,如果semflg值中包含了IPC_CREAT和IPC_EXCL,则失败并设置errno为EEXIST;否则返回一个已经存在的信号量集的句柄。
例1:创建一个包含三个信号量元素的新的信号量集合
#define PERMS    (S_IRUSR | S_IWUSR)
int semid;
if ((semid = semget(IPC_PRIVATE, 3, PERMS)) == -1)
 perror ( "Failed to create new private semaphore");
IPC_PRIVATE保证semget创建一个新的信号量集。
例2:访问由键值99887标识的只有一个元素的信号量集
#define PERMS (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH |S_IWOTH)
#define KEY      ((key_t) 99887)
int semid;
if ( (semid =semget(KEY, 1, PERMS | IPC_CREAT)) == -1)
perror("Failed to acess semaphore with key 99887");
如果从一个已经存在的建或者一个从路径名中导出的键中获得一个新的信号量集,必须使用IPC_CREAT,来说明创建一个新的信号量集(如果信号量已经存在,则返回现存信号量集的句柄)。如果同时指定IPC_CREAT和IPC_EXCL那么如果信号量集已经存在则返回一个错误
例3:
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/sem.h>
#include <sys/stat.h>
 
#define PERMS      (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
#define SET_SIZE   2
 
int main(int argc, char *argv[])
{
    key_t mykey;
    int    semid;
 
    if (argc != 3)
    {
        fprintf (stderr, "Usage: %s pathname id/n", argv[0]);
        exit(1);
    }
 
    if ( (mykey = ftok(argv[1], atoi(argv[2]))) == (key_t)-1 )
    {                       //由路径和ID唯一确定一个键值
        perror ("Failed to derive key form filename");
        exit(1);
    }
 
    if ( (semid = semget(mykey, SET_SIZE, PERMS | IPC_CREAT)) == -1 )
{      //如果该键值mykey没有与任何信号量集关联,则创建新的信号量集与其关联,
返回semid是新信号量集的描述符
如果有信号量集与mykey关联了,那么返回现存的信号量集描述符给semid.
        perror("Failed to create semaphore with key");
        exit(1);
    }
 
    printf ("semid = %d/n", semid);
    return 0;
}
执行结果:
./semfrompath ./semfrompath.c 998
semid = 65536
./ipcs -s
------ Semaphore Arrays --------
key        semid      owner      perms      nsems     
0xe6016f51 65536      bana      666        2        
再次执行会得到同一个semid。
如果修改ID执行./semfrompath ./semfrompath.c 997,则会得到新的信号量集描述符
删除:semid号为65536的信号量集
./ipcrm -s 65536
总结:信号量集的产生必须要有个键mykey(有三种方法产生该键值),然后调用semget函数生成该信号量集的描述符semid(如果信号量集已经存在则返回现有的信号量集的描述符,如果信号量集不存在则用mykey创建新的信号量集并返回描述符)。
semid相当于文件的描述符,信号量集相当于文件,键值相当于路径名和文件名。
2.2 信号量集的控制
信号量集中每个元素在使用之前,都应该用semctl进行初始化。
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, union semun arg);
如果成功返回一个非负的值,具体返回值取决于cmd的值。
cmd值GETVAL、GETPID、GETNCNT和GETZCNT使semctl返回与cmd相关的值。
如果成功,所有其它的cmd返回0。
如果不成功semctl返回-1并设置errno,必须检测的错误码:
EACCES    对调用程序来说,操作被否定
EINVAL     semid的值或cmd的值无效,或者semnum的值为负或者太大
EPERM     cmd的值为IPC_RMID或IPC_SET,且调用程序没有所要求的特权
ERANGE cmd为SETVAL或SETALL,而且要设置的值越界了
功能:
函数semctl为信号量集semid的semnum个元素提供了控制操作。参数cmd指定了操作类型,第四个参数arg可选,是否使用取决于cmd的值。
cmd的值:
GETALL       在arg.array中返回信号量集的值
GETVAL       返回一个特定信号量元素的值
GETPID        返回最后一个操纵元素的进程的进程ID
GETNCNT     返回等待元素增加的进程的个数
GETZCNT     返回等待元素变成零的进程的个数
IPC_RMID     删除semid标识的信号量集
IPC_SET       设置来之arg.buf的信号量集的权限
IPC_STAT      将信号量集semid的semid_ds结构成员拷贝到arg.buf中
SETALL       用arg.array来设置信号量集的值
SETVAL       将一个特定的信号量元素的值设定为arg.val
其中有几个命令需要一个arg参数来读取或存储结构
参数arg的类型为union semun,必须要定义这个类型的数据:
union semum
{
int                 val
struct semid_ds     *buf;
unsigned short       *array;
}arg;
例1:将指定的信号量元素的值设为semvalue
#include <sys/sem.h>
 
int initelement(int semid, int semnum, int semvalue)
{
    union semun            //必须定义
    {
        int               val;
        struct semid_ds   *buf;
        unsigned short    *array;
    }arg;
    arg.val = semvalue;
    return semctl(semid, semnum, SETVAL, arg);
}
成功返回0,不成功返回-1并设置errno
 
例2:删除semid指定的信号量集
#include <sys/sem.h>
 
int removesem(int semid)
{
    return semctl(semid, 0, IPC_RMID);
}
成功返回0,不成功返回-1并设置errno
 
2.3 信号量集的操作
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, size_t nsops);
成功返回0,不成功返回-1并设置errno,必须检测的错误码:
E2BIG     nsops的值太大
EACCES 对调用程序来说,操作被否定
EAGAIN 操作会阻塞进程但是(sem_flg&IPC_NOWAIT)!= 0
EFBIG     某一个sops条目的sem_num值小于0,或大于信号量集中元素的数目
EIDRM    信号量集标识符semid已经从系统中删除了
EINTR     semop被信号中断
EINVAL    semid的值无效,或者请求做SEM_UNDO操作的独立信号量集的数量
超出了限制
ENOSPC 已经超出了对请求SEM_UNDO的进程数的限制
ERANGE 操作会造成semval或semadj值得溢出
功能:
semop函数 在单个信号量集上原子的执行 sops 数组中指定的所有操作。如果其中任何一个单独的元素操作会使进程阻塞,进程就会阻塞而不会执行任何操作。
说明:
结构struct sembuf指定了一个信号量元素操作,包含下列成员。
short sem_num    信号量元素的数量( 信号量元素在信号量集中的序号)
short sem_op      要执行的特定元素操作
short sem_flg      为操作指定选项的标志符
sem_op如果是大于零的整数,semop就将这个值与sem_num号信号量元素相加,并唤醒所有等待该元素增加的进程。
       如果为零,且信号量元素值不为0,semop就会阻塞调用进程(进程在等待0),并增加等待那个信号量元素值变为零的进程计数。
      如果sem_op为负数,那么,如果结果不能为负的话,semop就将sem_op值添加到相应的信号量元素值上去。如果操作可能会使元素值为负,semop就将进程阻塞在使信号量元素值增加的事件上。如果结果值为0,那么semop就唤醒等待0的进程。
例:对struct sembuf成员初始化
#include <sys/sem.h>
void setsembuf(struct sembuf *s, int num, int op, int flg)
{
    s->sem_num = (short)num;
    s->sem_op = (short)op;
    s->sem_flg = (short)flg;
    return ;
}
注:struct sembuf myopbuf = {1, -1, 0};这样的初始化方式可能没问题,但是不建议用,原应是POSIX:XSI中没有说明成员出现的顺序,也没有限定struct sembuf中只能包含这些成员。
例2:setsembuf原子地将semid的零号元素加1,将semid的1号元素加2
struct sembuf myop[2];
setsembuf(myop,   0, 1, 0);
setsembuf(myop+1, 1, 2, 0);
if (semop(semid, myop, 2) == -1)
    perror("Failed to perform semaphore operation");
 
例3:有两个元素的信号量集S, 表示了一个磁带驱动器系统,其中进程1使用磁带机A、进程2使用磁带机A和B、进程3使用磁带机B。以下的信号量操作允许进程已互斥的方式对磁带驱动器进行访问。
struct sembuf get_tapes[2];
struct sembuf release_tapes[2];
 
setsembuf(get_tapes,       0, -1, 0);   //使零号信号量元素减1  类似wait
setsembuf(get_tapes+1,     1, -1, 0);   //使1号信号量元素减1   
setsembuf(release_tapes,   0, 1, 0);   //使零号信号量元素加1   类似signal
setsembuf(release_tapes+1, 1, 1, 0);   //使1号信号量元素加1
 
// 零号信号量元素 ß ->磁带机A
1号信号量元素 ß ->磁带机B
 
Process 1:   semop(S, get_tapes, 1);         //互斥使用磁带机A,使用零号信号量元素    
<use tape A>                   
            semop(S, release_tapes, 1);
 
Process 2:   semop(S, get_tapes, 2);     //互斥使用磁带机AB,使用零号和1号信号量元素
         <use tapes A and B>
         semop(S, release_tapes, 2);
 
Process 3:  semop(S, get_tapes+1, 1);   //互斥使用磁带机B,使用1号信号量元素
         <use tape B>
         semop(S, release_tapes+1, 1);
 
例4:遇到信号中断之后重启semop函数 (需要养成重启那些将errno设置为EINTR的函数的习惯)
#include <errno.h>
#include <sys/sem.h>
int r_semop(int semid, struct sembuf *sops, int nsops)
{
    while (semop(semid, sops, nsops) == -1)
    {
        if (errno != EINTR)
            return -1;
    }
    return 0;
}
 
例5:使用信号量集来保护临界区
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/sem.h>
#include <sys/stat.h>
#include <sys/wait.h>
 
#define BUFSIZE 1024
#define PERMS   (S_IRUSR | S_IWUSR)
//初始化信号量集中semnum指定的信号量元素的值为semvalue
int initelement(int semid, int semnum, int semvalue)
{
    union semun
    {
        int              val;
        struct semid_ds *buf;
        unsigned short   *array;
    }arg;
    arg.val = semvalue;
    return semctl(semid, semnum, SETVAL, arg);
}
//删除信号量集
int removesem(int semid)
{
    return semctl(semid, 0, IPC_RMID);
}
//对信号量集进行操作,主要是信号量元素的值得改变和进程阻塞之类
int r_semop(int semid, struct sembuf *sops, int nsops)
{
    while (semop(semid, sops, nsops) == -1)
    {
        if (errno != EINTR)
            return -1;
    }
    return 0;
}
//信号量集操作函数的操作变量的初始化函数
int setsembuf(struct sembuf *s, int num, int op, int flg)
{
    s->sem_num = (short)num;
    s->sem_op = (short)op;
    s->sem_flg = (short)flg;
    return;
}
 
void printerror(char *msg, int error)
{
    fprintf (stderr, "[%ld] %s: %s/n", (long)getpid(), msg, strerror(error));
}
 
int main(int argc, char *argv[])
{
    char           buffer[BUFSIZE];
    char           *c;
    pid_t          childpid;
    int            delay;
    int            error;
    int            i;
    int            j;
    int            n;
    int            semid;
    struct sembuf semsignal[1];
    struct sembuf semwait[1];
 
    if ((argc != 3) || ((n=atoi(argv[1])) <= 0) || ((delay=atoi(argv[2])) < 0))
    {
        fprintf (stderr, "Usage: %s process, delay/n",argv[0]);
        return 1;
    }
//创建信号量集,只有一个信号量元素
    if ((semid = semget(IPC_PRIVATE, 1, PERMS)) == -1)
    {
        perror("Failed to create a private semaphore");
        return 1;
    }
 
    setsembuf(semwait,   0, -1, 0);                   
    setsembuf(semsignal, 0, 1, 0);
 
    if (initelement(semid, 0, 1) == -1) //初始化信号量集中0号信号量元素
    {
        perror ("Failed to initialize semaphore element to 1");
        if (removesem(semid) == -1 )
            perror ("Failed to remove failed semphore");
        return 1;
    }
 
    for (i=1; i<n; i++)
    {
        if ((childpid = fork()) != 0)      //父进程推出循环,子进程继续循环
            break;
    }
 
    snprintf (buffer, BUFSIZE, "i: %d PID: %ld , parent PID: %ld, child PID: %ld/n",
            i, (long)getpid(), (long)getppid(), (long)childpid);
    c = buffer;
                          //对信号量元素执行-1操作(WAIT)
    if (((error = r_semop(semid, semwait, 1)) == -1) && (i>1))
    {
        printerror("Child failed to lock semid", error);
        return 1;
    }
                       //执行临界区代码
    while (*c != '/0')
    {
        fputc (*c, stderr);
        c++;
        for (j=0; j<delay; j++);
    }
                    //对信号量元素执行+1操作(SIGNAL)
    if ((error = r_semop(semid, semsignal, 1)) == -1)
    {
        printerror ("Failed to unlock", error);
    }
 
    if ((wait(NULL) == -1) && (errno != ECHILD))
        printerror("Failed to wait", errno);
    if ((i==1) && ((error=removesem(semid)) == -1)) //删除信号量集
    {
        printerror("Failed to clean up", error);
        return 1;
    }
    return 0;
}
./chainsemset 3 2
i: 3 PID: 2547 , parent PID: 2546, child PID: 0
i: 2 PID: 2546 , parent PID: 2545, child PID: 2547
i: 1 PID: 2545 , parent PID: 2511, child PID: 2546
信号量集的使用步骤:
a.创建信号量集
b.对信号量集中的信号量元素进行初始化
c.利用信号量元素进行互斥使用
d.删除信号量集
 
信号量集和信号量的关系:
信号量集可以反映多种类型多个资源的临界区问题
信号量最多只能反映单个类型多个资源的临界区问题
互斥锁只能反映单个类型单个资源的临界区问题
 
3 .POSIX :XSI 共享内存
共享内存允许进程对相同的内存段进行读和写。
头文件sys/shm.h为共享内存定义了数据结构,其中包括shmid_ds,它的成员如下:
struct ipc_perm shm_perm;       //操作权限结构
size_t           shm_segsz;      //用字节表示的段的长度
pid_t            shm_lpid;       //最后一个操作的进程ID
pid_t            shm_cpid;       //创建者的进程ID
shmatt_t         shm_nattch      //当前连接的进程数量
time_t           shm_atime;      //最后一次调用shmat的时间
time_t           shm_dtime;      //最后一次调用shmdt的时间
time_t           shm_ctime;      //最后一次调用shmtl的时间
shmatt_t的数据类型是一个无符号整数数据类型,这种类型至少要和unsigned short一样大。
3.1 访问(创建)一个共享的内存段
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg); //size指的是共享内存段的格式n*sizeof(int)则共享内存段将用来存储int类型数据n个
成功返回一个对应于共享内存段标识符的非负整数,不成功返回-1并设置errno,错误码:
EACCES     key的共享标识符存在,但没有授予相关的权限
EEXIST      key的共享标识符存在,但((shmflg&IPC_CREAT) && (shmflg&IPC_EXCL)!=0
EINVAL      要创建共享内存段,但size是无效的
EINVAL      没有共享内存段要创建,但size与系统设置的限制或与key所代表的共享段的长度不相符
ENOENT     key的共享内存表示符不存在,但(shmflg&IPC_CREAT)== 0
ENOMEM    没有足够的内存空间来创建指定的共享内存段
ENOSPC     要超出系统范围内对共享标识符的限制了
功能:
shmget函数返回一个与参数key相关的共享内存段标识符。
如果键位IPC_CREAT或者shmflg&IPC_CREAT非零,而且没有共享内存段或标识符与key相关联,函数就创建这个段,共享内存段被初始化为零。
3.2 共享内存段的连接和分离
连接:
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
成功返回内存段的起始地址,不成功shmat返回-1并设置errno,必须检测的错误码:
EACCES    调用程序的操作权限别否定
EINVLA     shmid和shmaddr的无效
EMFILE     连接到进程上的共享内存段的树木超出了限制
ENOMEM 进程数据空间不足以容纳共享内存段
功能:
函数将shmid指定的共享内存段连接到调用进程的地址空间,并为shmid增加shm_nattch的值。
函数返回一个void *指针,这样程序就可以象使用普通指针一样使用返回值了。
用NULL作为shmaddr的值。
有些西贡可能需要设置shmflg,以正确对其内存段。
分离:
#include <sys/shm.h>
int shmdt(const void *shmaddr);
成功返回0,不成功返回-1并设置errno,错误码:
EINVAL shmaddr不对应于共享内存段的起始地址
功能:
用完一个共享内存,调用其来分离共享内存段,并对shm_nattch进行减操作。
最后一个分离共享内存段的进程应该通过调用 shmctl 来释放共享内存段
3.3 控制共享内存
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
成功返回0,不成功返回-1并设置errno,错误码:
EACCES    cmd为IPC_STAT,但是调用程序没有读权限
EINVAL    shmid或cmd的值无效
EPERM    cmd为IPC_RMID或IPC_SET,调用程序没有正确的权限
cmd值:
IPC_RMID 删除共享内存段,并销毁相应的shmid_ds
IPC_SET    用buf中的值来设置共享内存段shmid的字段值
IPC_STAT 将共享内存段shmid中的当前值拷贝到buf中去
3.4 共享内存实例
例1:两个进程,一人一个文件描述符,进行读取操作,并在共享内存段记录字节信息,
最终在同一个进程中访问共享内存段的记录信息,并打印
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/wait.h>
 
#define PERM     (S_IRUSR | S_IWUSR)
#define BUFSIZE 1024
 
int detachandremove(int shmid, void *shmaddr)
{
    int error = 0;
 
    if (shmdt(shmaddr) == -1)
    {
        error = errno;
    }
   
    if ((shmctl(shmid, IPC_RMID, NULL) == -1) && (error == 0))
    {
        error = errno;
    }
   
    if (error == 0)
    {
        return 0;
    }
   
    errno = error;
    return -1;
}
 
int readwrite(int fromfd, int tofd)
{
    char buf[BUFSIZE];
    int readbytes;
    if ((readbytes = read(fromfd, buf, BUFSIZE)) == -1)
        return -1;
    if ( readbytes == 0 )
        return 0;
   
    if (write(tofd, buf, readbytes) == -1)
        return -1;
   
    return readbytes;
}
 
int main(int argc, char *argv[])
{
    int byteread;
    int childpid;
    int fd;
    int fd1;
    int fd2;
    int id;
    int *sharedtotal;
    int totalbytes = 0;
 
    if (argc != 3)
    {
        fprintf (stderr, "Usage: %s file1 file2/n", argv[0]);
        return 1;
    }
 
    if ( ((fd1 = open(argv[1], O_RDONLY)) == -1) ||
        ((fd2 = open(argv[2], O_RDONLY)) == -1))
    {
        perror ("Failed to open file");
        return 1;
    }
 
    if ((id = shmget(IPC_PRIVATE, sizeof(int), PERM)) == -1)
    {
        perror ("Failed to create shared memory segment");
        return 1;
    }
 
    if ((sharedtotal = (int *)shmat(id, NULL, 0)) == (void *)-1)
    {
        perror ("Failed to attach shared memory segment");
        if (shmctl(id, IPC_RMID, NULL) == -1)
            perror ("Failed to remove memory segment");
        return 1;
    }
 
    if ((childpid = fork()) == -1)
    {
        perror ("Failed to create chidl process");
        if (detachandremove(id, sharedtotal) == -1)
            perror ("Failet to destroy shared memory segment");
        return 1;
    }
 
    if (childpid > 0)
        fd = fd1;
    else
        fd = fd2;
   
    while ((byteread = readwrite(fd, STDOUT_FILENO)) > 0)
        totalbytes += byteread;
   
    if (childpid == 0)
    {
        *sharedtotal = totalbytes;       //子进程中的访问
        return 0;
    }
   
    if (wait(NULL) == -1)
        perror ("Failedt to wait for child");
    else
    {
        fprintf (stderr, "Bytes copied: %8d by parent/n", totalbytes);
        fprintf (stderr, "              %8d by child/n", *sharedtotal); //父进程中访问
        fprintf (stderr, "              %8d by total/n", totalbytes + *sharedtotal);
    }
 
    if (detachandremove (id, sharedtotal) == -1)
    {
        perror ("Failed to destroy sharde memory segment");
        return 1;
    }
    return 0;
}
注1:上面的程序通过祖先进程共享内存地址访问到了,共享内存段的描述符。
注2:在没有共同祖先的进程间使用共享内存要求键值达成一致,进程可以直接协商,也可以通过ftok和路径名来协商。
 
例2:函数在共享内存中进行同步的求和和计数
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <errno.h>
#include <sys/stat.h>
#include <time.h>
 
#define PERM (S_IRUSR | S_IWUSR)
 
typedef struct
{
    int count;
    double sum;
    sig_atomic_t ready;
}shared_sum_t;
 
static int            semid;             //信号量集描述符
static struct sembuf semlock;          //信号量集操作变量
static struct sembuf semunlock;
static shared_sum_t   *sharedsum;       //共享内存地址变量
 
//初始化信号量元素
int initelement(int semid, int semnum, int semvalue)
{
    union semun
    {
        int val;
        struct semid_ds *buf;
        unsigned short *array;
    }arg;
    arg.val = semvalue;
    return semctl(semid, semnum, SETVAL, arg);
}
 
//创建单个信号量元素的信号量集
int initsemset(key_t mykey, int value, sig_atomic_t *readyp)
{
    int semid;
    struct timespec sleeptime;
 
    sleeptime.tv_sec = 0;
    sleeptime.tv_nsec = 10000000L;
    semid             = semget(mykey, 1, PERM | IPC_CREAT | IPC_EXCL);
    if ((semid == -1) && (errno != EEXIST))
        return -1;
    if (semid >= 0)
    {
        if (initelement(semid, 0, value) == -1)
            return -1;
        *readyp = 1;
        return semid;
    }
 
    if ((semid = semget(mykey, 1, PERM)) == -1)
        return -1;
   
    while (*readyp == 0)
        nanosleep(&sleeptime, NULL);
    return semid;
}
 
//信号量集操作变量的初始化
void setsembuf(struct sembuf *s, int num, int op, int flg)
{
    s->sem_num = (short)num;
    s->sem_op = (short)op;
    s->sem_flg = (short)flg;
    return ;
}
 
//创建共享内存段
int initshared(key_t key)
{
    int shid;
 
    setsembuf(&semlock, 0, -1, 0);
    setsembuf(&semunlock, 0, 1, 0);
   
    shid = shmget(key, sizeof(shared_sum_t), PERM | IPC_CREAT | IPC_EXCL);
    if ((shid == -1) && (errno != EEXIST))
        return -1;
    if ( shid == -1)
    {
        if ( ((shid = shmget(key, sizeof(shared_sum_t), PERM)) == -1) ||
            ((sharedsum = (shared_sum_t *)shmat(shid, NULL, 0)) == (void *)-1))
            return -1;
       
    }
    else
    {
        sharedsum = (shared_sum_t *)shmat(shid, NULL, 0);
        if (sharedsum == (void *)-1)
            return -1;
        sharedsum->count = 0;
        sharedsum->sum   = 0.0;
    }
    semid = initsemset(key, 1, &sharedsum->ready);
    if (semid == -1)
        return -1;
    return 0;
}
 
//访问共享内存段,用信号量集实现互斥
int add(double x)
{
    if (semop(semid, &semlock, 1) == -1)
        return -1;
    sharedsum->sum   += x;
    sharedsum->count ++;
    if (semop(semid, &semunlock, 1) == -1)
        return -1;
    return 0;
}
 
//访问共享内存段,用信号量集实现互斥
int getcountandsum(int *countp, double *sum)
{
    if (semop(semid, &semlock, 1) == -1)
        return -1;
    *countp = sharedsum->count;
    *sum    = sharedsum->sum;
    if (semop(semid, &semunlock, 1) == -1)
        return -1;
    return 0;
}
 
       
//信号操作函数  
static void showit(int signo)
{
    int     count;
    double sum;
 
    if (getcountandsum(&count, &sum) == -1)
        printf("Failed to get count and sum/n");
    else
        printf("sum is %f and count is %d/n", sum, count);
}
 
int main(int argc, char *argv[])
{
    struct sigaction act;
    int              key;
    sigset_t         mask;
    sigset_t         oldmask;
 
    if (argc != 2)
    {
        fprintf (stderr, "Usage: %s key/n", argv[0]);
        return 1;
    }
    key = atoi(argv[1]);
    if ( initshared(key) == -1 )
    {
        perror ("Failed to initialize shared memory");
        return 1;
    }
    if ((sigfillset(&mask) == -1) || (sigprocmask(SIG_SETMASK, &mask, &oldmask) == -1))
    {
        perror ("Failed to block signals to set up handler");
        return 1;
    }
    printf ("This is process %ld waiting for SIGUSR (%d)/n",
            (long)getpid(), SIGUSR1);
    act.sa_handler = showit;
    act.sa_flags   = 0;
 
    if ((sigemptyset(&act.sa_mask) == -1 ) || (sigaction(SIGUSR1, &act, NULL) == -1))
    {
        perror ("Failed to set up signal handler");
        return 1;
    }
 
    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) == -1)
    {
        perror("Failed to unblock signals");
        return 1;
    }
 
 
for (;;);
    pause();
}
 
注1:用共享内存段实现信息的共享,用信号量集实现互斥。
注2:共享内存段地址和信号量描述符都使用静态全局变量。
注3:运行程序,然后在另外一个窗口发送SIGUSR1信号给进程
 
4 :POSIX :XSI 消息队列
消息队列是一种进程间通信机制,它允许进程向其他进程发送消息以及从其他进程接收消息。消息队列的数据结构定义在sys/msg.h中,主要的数据结构为msqid_ds,包括成员:
struct    ipc_perm   msg_perm          //操作权限结构
msgqnum_t         msg_qnum;          //当前队列中的消息数量
msglen_t           msg_qbytes;          //队列中允许的最大字节数
pid_t             msg_lspid;           //msgsnd的进程ID
pid_t              msg_lrpid;           //msgrcv的进程ID
time_t             msg_stime;          //最后一次执行msgsnd的时间
time_t             msg_rtime;          //最后一次执行msgrcv的时间
time_t             msg_ctime;          //最后一次执行msgctl的时间
msgqnum_t和 msglen_t这两种数据类型必须至少和unsigned short一样大。
4.1 访问消息队列
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
成功返回对应于消息队列标识符的非负整数,不成功返回-1并设置errno,错误码:
EACCES 存在key对应的消息队列,但权限被否定
EEXIST    存在key对应的消息队列,同时((msgflg& IPC_CREAT)&& (msgflg&IPC_EXCL)) != 0
ENOENT     不存在key对应的消息队列,同时(msgflg& IPC_CREAT) == 0
ENOSPC     要超出系统范围内对消息队列的限制了
4.2 向队列插入消息
#include <sys/msg.h>
int msgsnd(int msgqid, const void *msgp, size_t msgsz, int msgflg);
成功返回0, 不成功返回-1并设置errno,错误码:
EACCES         对调用程序来说,调用被否定
EAGAIN         操作会阻塞进程,但(msgflg & IPC_NOWAIT) != 0
EIDRM          msqid已经从系统中删除了
EINTR           函数被信号中断
EINVAL          参数msqid无效,消息类型<1,或者msgsz越界了
将向队列(在程序外部,系统内已经分配)中插入 msgp 指向的消息
注:参数msgp指向用户定义的缓冲区,他是如下的结构
struct mymsg
{
 long mtypes;          消息类型
 char *mtext;         消息文本
}mymsg_t
用户可以用任何适用于应用程序的方式来分配消息类型
例:向消息队列发送字符串mymessage的步骤
a.分配一个类型为mymsg_t, 长度为sizeof(mymsg_t) +strlen(mymessage)的缓冲区mbuf
b.将mymessage拷贝到mbuf->mtext成员中去
c.在mbuf->mtype成员中设置消息类型
d.发送消息
e.释放mbuf
 
4.3 从队列中删除消息
4.4 消息队列的控制
 
 
5 .练习:POSIX 无名信号量
6 .练习:POSIX 命名信号量
7 .练习:用共享内存实现管道
8 .练习:用消息队列实现管道
 
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值