LinuxC编程进程间的通信总结

LinuxC编程进程间的通信总结


本文参考《LinuxC编程实战》

前言

进程的空间地址是各自独立的,因此进程间进行数据交流就需要特定的通信机制,在大型应用中往往需要多个进程同时工作,这就需要进程间的数据交流和配合。
进程间的通信方式有:

  • 管道(pipe):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程间的亲缘关系一般指父子进程关系。
  • 有名管道(named pipe):有名管道也是半双工管道通信方式,但是它允许不具有亲缘关系的进程间的通信。
  • 信号量(semophore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程在访问共享资源时,其他进程也在访问,因此主要作为进程间或同一进程间不同线程之间的同步机制。
  • 消息队列(message queue):消息队列是消息地列表。消息列表克服了信号传递信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  • 信号(signal):信号用于通知接收进程某个事件已经发生。在我的另一篇博客有总结:信号总结
  • 共享内存(shared memory):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建但多个进程可以访问。它往往与其他通信机制如信号量配合使用,用来实现进程间的同步和通信。
  • 套接字(socket):套接口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。

管道

管道是一种两个进程间进行通信的机制,它有一些局限性:

  • 管道只能单项通信,及只能由一个进程流向另一个进程,如果要实现双向通信就要创建两个管道。
  • 管道只能用于父子进程或兄弟进程间的通信。
  • 管道没有名字,管道的缓存区大小PIPE_BUF受限,管道传递的是无格式的字节流。
    管道就想是一个特殊的文件,在创建文件时系统为它设置了一个文件描述符,进程把管道当作文件进行操作。在创建管道是系统为管道分配了一个页面作为数据缓存区,进程通过对这个数据缓存区读写进行通信,一个进程写入数据,另一个进程读出数据。
    管道的创建和读写
    管道的创建使用的是pipe函数,该函数如果调用成功返回0,并且数组中包含两个新的文件描述符;如果失败,返回-1.函数原型为:
#include <unistd.h>
int pipe(int fd[2]);

管道两端分别用描述符fd[0],fd[1]来描述。需要注意的是管道两端的任务是固定的,一端只能用于读,由描述符fd[0]表示,为读端;另一端只能用于写,由描述符fd[1]表示,为写端;如果试图从读端写入会出错。管道是一种文件,所以可以使用文件的I/O函数,如read(),write()等;
管道的使用方法:
(1).创建一个管道;
(2).创建子进程;
(3).父进程关闭管道读端,负责写入数据;
(4).子进程关闭管道写端,负责读出数据;
例:pipe.c

/*************************************************
 * 用管道创建单向传递功能
 * **********************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

void read_from_pipe(int fd)       //读管道
{
    char message[100];
    read(fd, message, 100);
    printf("read from pipe:%s",message);
}

void write_to_pipe(int fd)       //写管道
{
    char *message = "Hello, pipe!\n";
    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 -1:
        {
            printf("fork error!\n");
            exit(1);
        }
        case 0:
        {
            close(fd[1]);      //子进程关闭fd1
            read_from_pipe(fd[0]);
            exit(0);
        }
        default:
        {
            close(fd[0]);             //父进程关闭fd2
            write_to_pipe(fd[1]); 
            wait(&stat_val);          //等待子进程
            exit(0);
        }
    }

    return 0;
}

dual_pipe.c

/*************************************************
 * 用两个管道创建双向传递功能
 * **********************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

void child_rw_pipe(int readfd, int writefd)
{
    char *message1 = "from child process!\n";
    write(writefd, message1,strlen(message1)+1);                    //写入管道

    char message2[100];
    read(readfd, message2,100);                                     //读管道
    printf("child process read from pipe:%s",message2);
}

void parent_rw_pipe(int readfd, int writefd)
{
    char *message1 = "from parent process!\n";
    write(writefd, message1,strlen(message1)+1);                    //写管道

    char message2[100];
    read(readfd, message2,100);
    printf("parent process read from pipe:%s",message2);            //读管道
}

int main()
{
    int pipe1[2],pipe2[2];
    pid_t pid;
    int stat_val;

    printf("realize full-duplex communication:\n\n");
    if(pipe(pipe1))                                                 //创建管道pipe1 父写子读
    {
        printf("pipe1 failed!\n");
        exit(1);
    }
    if(pipe(pipe2))                                                 //创建管道pipe2 父读子写
    {
        printf("pipe2 failed!\n");
        exit(1);
    }

    pid = fork();
    switch(pid)
    {
        case -1:
        {
            printf("fork error!\n");
            exit(1);
        }
        case 0:
        {
            //子进程
            close(pipe1[1]);                       //关闭写
            close(pipe2[0]);                       //关闭读
            child_rw_pipe(pipe1[0],pipe2[1]);
            exit(0);
        }
        default :
        {
            close(pipe1[0]);                       //关闭读
            close(pipe2[1]);                       //关闭写
            parent_rw_pipe(pipe2[0],pipe1[1]);
            wait(&stat_val);
            exit(0);
        }
    }
}

有名管道

管道有一个局限性是没有名字,因此只能在具有亲缘关系的进程间才可以使用,有名管道(named pipe或FIFO)的出现客服了这一限制。FIFO与管道不同之处在于它提供了一个路径名与之关联,以FIFO的文件形式储存于文件系统中。有名管道是一个设备文件,因此可以不局限与具有亲缘关系才能访问。
有名管道的创建和读写
创建有名管道的方法有两种,一种是在shell下创建,另一种是在程序中用函数创建。shell中使用mknod或者mkfifo命令,例如:

mknod namedpipe

创建有名管道的函数有两个:mknod和mkfifo。函数原型为:

#include <sys/types.h>
#include <sys/sta.h>
int mknod(const char *path, mode_t mod, dev_t dev);
int mkfifo(const char *path, mode_t mode);

函数mknod中path参数为创建的有名管道的全路径名;mod为创建有名管道的模式,指名其权限;dev为设备值,该值文件创建的类型,它只在创建设备文件时才会用到。这两个函数调用成功返回0,失败返回-1。mkfifo的参数与mknod的前两个参数一样。
使用时如下:

//1.0
umask(0);
if(mknod("/tmp/fifo",S_IFIFO | 0666, 0) == -1)
{
	perror("mknod error!");
	exit(1);
}

//2.0
umask(0);
if(mkfifo("/tmp/fifo",S_IFIFO | 0666) == -1)
{
	perror("mknod error!");
	exit(1);
}

umask(0)用于在创建新文件时屏蔽掉不应该有的访问允许权限。S_IFIFO | 0666指创建一个有名管道且存取权限为0666。
下面我们用一个有名管道实现两个无亲缘关系的进程间的通信:
procread.c

/*************************************
 * 先运行procwrite.c文件,写入有名管道
 * 后运行procread.c文件,读出有名管道中的内容
 * 如果已经有了有名管道myfifo则先删除它
 * **********************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define FIFO_NAME       "myfifo"
#define BUF_SIZE        1024

int main()
{
    int fd;
    char buf[BUF_SIZE];

    umask(0);                           //给予最大权限
    fd = open(FIFO_NAME, O_RDONLY);      //以只读的方式打开文件myfifo
    read(fd, buf, BUF_SIZE);             //读取文件myfifo
    printf("Read content: %s\n", buf);
    close(fd);                           //关闭文件

    return 0;

procwrite.c

/*************************************
 * 先运行procwrite.c文件,写入有名管道
 * 后运行procread.c文件,读出有名管道中的内容
 * 如果已经有了有名管道myfifo则先删除它
 * **********************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#define FIFO_NAME       "myfifo"
#define BUF_SIZE        1024

int main()
{
    int fd;
    char buf[BUF_SIZE] = "有名管道";
    char A[100] = "ABC";

    umask(0);
    if(mkfifo(FIFO_NAME, S_IFIFO | 0666) == -1)          //创建一个有名管道
    {
        perror("mkfifo error!");
        exit(1);
    }
    if((fd = open(FIFO_NAME, O_WRONLY)) == -1)           //以写的方式打开一个有名管道
    {
        perror("open error!");
        exit(1);
    }
    write(fd, buf,strlen(buf)+1);                        //向管道内写入内容,并等待na_pipe进程读出myfifo中的内容
    write(fd,A,strlen(A)+1);                             //该内容没有被写入或者读出
    printf("na_pipe 读出完毕\n");
    
    close(fd);                                           //关闭
    
    return 0;
}

如果要实现双向通信需要使用两个有名管道。

消息队列

概念
消息队列是一个存放在内核中的消息链表,每个消息队列由消息队列标识符标识。与管道不同的是消息队列存放在内核中,只有在内核重启(即操作系统重启)或者显式地删除-一个消息队列时,该消息队列才会被真正删除。
操作消息队列时,需要用到- -些数据结构,熟悉这些数据结构是掌握消息队列的关键。下面介绍几个重要的数据结构。

1.消息缓冲结构

向消息队列发送消息时,必须组成合理的数据结构。Linux系统定义了-一个模板数据结构msgbuf:

#include <linux/msg.h>
struct msgbuf{
	long mtype;
	char mtext[1];
}

结构体中的mtype字段代表消息类型。给消息指定类型,可以使得消息在一一个队列中重复使用。nmtext字段指消息内容。
注意: mtext虽然定义为char类型,并不代表消息只能是一个字符,消息内容可以为任意类型,由用户根据需要定义。如下面就是用户定义的一个消息结构:

struct mymsgbuf{
	long mtype;
	struct stdudent stu;
};

消息队列的大小受到MSGMAX的限制,MSGMAX为最大值。
2.msqid_ds内核数据结构
每个消息队列都有一个msqid_ds结构体,此结构体保存着该消息队列当前的状态信息。

struct msqid_ds{
	struct ipc_perm msg_perm;       //保存了消息队列的存取权限和队列用户ID,组ID等信息
	struct msg *msg_first;          //指向队列中第一条消息
    struct msg *msg_last;           //指向队列中最后一条消息
    __kernel_time_t msg_stime;      //向消息队列发送最后一条信息的时间
    __kernel_time_t msg_rtime;      //从消息队列中读取最后一条消息的时间
    __kernel_time_t msg_ctime;      //最后一个变更消息队列时间
    unsigned long  msg_lcbytes;    
    unsigned long  msg_lqbytes;     
    unsigned short msg_cbytes;      //消息队列中所有消息所占的字节数
    unsigned short msg_qnum;        //消息队列中消息的数目
    unsigned short msg_qbytes;      //消息队列的最大字节数
    __kernel_ipc_pid_t msg_lspid;   //向消息队列发出最后一条消息的进程ID
    __kernel_ipc_pid_t msg_lrpid;   //从消息队列读出最后一条消息的进程ID
};
  1. ipc_perm内核数据结构
    结构体ipc_perm保存着消息队列的一些重要信息,如保存了消息队列的存取权限和队列用户ID,组ID等信息:
#include <linux/ipc.h>
struct ipc_perm{
	__kernel_key_t     key;    //创建消息队列用到的健值key
	__kernel_uid_t     uid;    //消息队列的用户ID
	__kernel_gid_t     gid;    //消息队列的组ID
	__kernel_uid_t     cuid;   //创建消息队列的进程用户ID
	__kernel_gid_t     cgid;   //创建消息队列的进程组ID
	__kernel_ mode_t   mode;
	__unsigned_short   seq;
}

消息队列的创建
消息队列是随着内核的存在而存在的,每个消息队列在系统范围内对应唯一的键值。要获取一个消息队列的描述符,只需提供该消息队列的键值及可,该键值通常由函数ftok返回,该函数原型为:

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);

参数pathname为路径,且一定要有访问权限;参数proj_id的值为1~255。
例如:
ftok.c

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/types.h>

int main()
{
    int i;
    for(i = 1; i <= 5; i++)
    {
        printf("key[%d] = %ul\n", i, ftok(".",i));
        //ftok函数根据键值生成唯一的键值
    }

    return 0;
}

有了键值我们就可以根据键值创建或打开一个消息队列,函数为msgget,该函数调用成功返回一个消息队列描述符,错误返回-1,函数原型为:

int msgget(key_t key, int msgflg);

key参数为键值,msgflg为一个标志参数,它的取值有:

  • IPC_CREATE:如果内核中不存在键值与key相等的消息对列,则新建一-个消息队列:如果存在这样的消息队列,返回该消息队列的描述符。

  • IPC_ EXCL: 和IPC _CREATE -起使用,如果对应键值的消息队列已经存在,则出错。
    注意:IPC_EXCL需要和IPC_CREATE一起使用否者无意义。
    写消息队列
    用函数msgsnd向消息队列发送(写)数据,该函数调用成功返回0,错误返回-1,该函数原型为:

#include <sys/msg.h>
int msgsnd(int msqid, struct msgbuf *msgp, size_t msgsz, int magflg);

各参数含义如下:

  • msqid:函数向msgid标识的消息队列发送一个消息
  • msgp:msgp指向发送的消息
  • msgsz:要发送消息的大小,不包含消息类型占用的4个字节
  • magflg:操作标志位,可以设置为0和IPC_NOWAIT。如果是0,则当消息队列已满的时候,msgsnd 将会阻塞,直到消息可以写进消息队列:如果magflg 为IPC_ NOWAIT,当消息队列已满的时候,msgsnd 函数将不等待立即返回。
    例:
    sendmsg.c
/***********************************
 * 创建一个消息队列,并写入消息
 * 编译运行该文件
 * 在shell下输入ipcs命令
 * 系统内部生成了一个消息队列Message Queues,其中含有一条消息
 * ********************************/
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

#define BUF_SIZE             256
#define PROJ_ID              32
#define PATH_NAME            "."

int main()
{
    struct mymsgbuf{
        long msgtype;
        char ctrlstring[BUF_SIZE];
    }msgbuffer;
    int   qid;                          //消息队列标识符
    int   msglen;
    key_t msgkey;

    if((msgkey = ftok(PATH_NAME, PROJ_ID)) == -1)  //获取键值
    {
        perror("ftok error!\n");
        exit(1);
    }

    if((qid = msgget(msgkey, IPC_CREAT | 0660)) == -1)  //创建消息队列
    {
        perror("msgget error!\n");
        exit(1);
    }

    msgbuffer.msgtype = 3;
    strcpy(msgbuffer.ctrlstring, "Hello,message queue");
    msglen = sizeof(struct mymsgbuf) - 4;
    if(msgsnd(qid,&msgbuffer, msglen, 0) == -1)        //将msgbuffer结构中的信息写入消息中
    {
        perror("msgget error!\n");
        exit(1);
    }

    return 0;
}

读消息队列
将消息写入消息队列或就可以用函数msgecv将消息读出,该函数调用成功返回读出的实际字节数,错误返回-1,函数原型为:

#include <sys/msg.h>
int msgrcv(int msqid, struct msgbuf *msgp, size_t msgsz, long int msgtyp, int msgflg);

参数含义为:

  • msqid:消息队列描述符。
  • 读取的消息存储到msgp指向的消息结构中。
  • msgsz:消息缓冲区的大小。
  • msgtyp:为请求读取的消息类型。
  • msgflg:操作标志位。msgflg 可以为IPC_NOWAIT, IPC_ EXCEPT,IPC_NOERROR
    3个常量。这些值的意义分别为: IPC_NOWAIT,如果没有满足条件的消息,调用立即返回,此时错误码为ENOMSG; IPC_EXCEPT与msytp配合使用,返回队列中第一一个类型不为msgtyp的消息; IPC_NOERROR,如果队列中满足条件的消息内容大于所请求的msgsz 字节,则把该消息截断,截断部分将被丢弃。
    例:前一个程序想消息队列写入了信息,我们将它读出:
    msgrcv.c
/**************************************
 * 从消息队列中读取消息
 * ***********************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define BUF_SIZE       256
#define PROJ_ID        32
#define PATH_NAME      "."

int main()
{
    struct mymsgbuf{
        long msgtype;
        char ctrlstring[BUF_SIZE];
    }msgbuffer;
    int    qid;      //消息队列标识符
    int    msglen;   
    key_t  msgkey;

    if((msgkey = ftok(PATH_NAME, PROJ_ID)) == -1)   //获取键值
    {
        perror("ftok error!\n");
        exit(1);
    }

    if((qid = msgget(msgkey, IPC_CREAT | 0660)) == -1)   //获取消息队列标识符
    {
        perror("msgget error!\n");
        exit(1);
    }

    msglen = sizeof(struct mymsgbuf) - 4;
    if(msgrcv(qid, &msgbuffer, msglen, 3, 0) == -1)     //将消息读入到msgbuffer结构中
    {
        perror("msgrcv error!\n");
        exit(1);
    }
    printf("Get message %s\n",msgbuffer.ctrlstring);
    return 0;
}

获取和设置消息队列的属性
消息队列的属性在结构体msqid_ds中,用户可以通过函数msgctl来获取和设置消息队列的属。该函数原型为:

#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

该函数对msqid为标识符的消息队列执行cmd操作:

  • IPC_STAT:获取msqid_ds结构体
  • IPC_SET:将buf结构体中的属性设置d到msqid_ds中
  • IPC_RMID:从内核中删除msqid标识的消息队列

信号量

概念
信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程在访问共享资源时,其他进程也在访问,因此主要作为进程间或同一进程间不同线程之间的同步机制。与消息队列一样,每个信号集都有一个semid_ds数据结构.

#include <linux/sen.h>
struct semid_ds{
	struct ipc_perm     sem_perm;   //对信号进行操作的许可权
	__kernel_time_t     sem_otime;  //对信号进行操作的最后时间
	__kernel_time_t     em_ctime;   //对信号进行修改的最后时间
	struct sem          *sembase;   //指向第一个信号
	struct sem_queue    sem_pending; //等待处理的挂起操作
	struct sem_queue    **sem_pending_last;  //最后一个正在挂起的操作
	struct sem_undo     *undo;       //撤销的请求
	ushort              sem_nsems;   //数组中的信号数
}

注意:这里的信号与之前学的信号不一样,该函数调用成功返回一个信号集的标识符,失败返回-1,上面semid_ds结构体不懂也无妨。
信号集的创建和打开
我们可以使用semget函数创建或打开信号集,函数原型为:

#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);

参数key为ftok获取的键值,nsems为要创建信号集所包含的个数,如果只打开一个信号集则为0,semflg为操作标志,可取以下值:

  • IPC_CREATE:调用semget()时,它会将此值与与key比较,如果相同则打开已存在的信号集,否则创建一个新的信号集
  • IPC_EXCL:与IPC_CREATE一起使用,当信号集已经存在则返回错误。
    信号量的操作
    当信号量大于0时表示可以使用的资源数量,小于0时表示等待使用资源的进程个数。信号量的值仅能通过PV操作来改变,PV操作通过函数semop实现,该函数原型为:
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, size_t nsops);

semid参数为信号集标识符; sops参数为指向进行操作的结构体数组首地址;参数nsops为要进行操作的信号的个数;该函数调用成功返回0,失败返回-1.

#include <linux/sem.h>
struct sembuf{
	ushort sem_num;      //信号在信号集中的索引
	short  sem_op;       //操作类型
	short  sem_flg;      //操作标志
}

在这里插入图片描述
P操作:将信号量-1,表示又有一个进程调用了资源;

//P操作函数   信号-1
int sem_P(int semid, int index)             //index = 0
{
    struct sembuf buf = {0, -1, IPC_CREAT};  //信号在信号集中的索引->0   
                                             //操作类型->-1   信号加上-1即-1
                                             //操作标志->IPC_CREAT

    if(index < 0)
    {
        perror("index of array cannot equals a minus value!");
        return -1;
    }

    buf.sem_num = index;                    //信号在信号集中的索引->0
    if(semop(semid , &buf, 1) == -1)        //信号-1
    {
        perror("a wrong operation to semaphore occurred!");
        return -1;
    }
    return 0;
}

V操作:将信号量+1,表示有一个进程使用完了资源;

//V操作函数   信号+1
int sem_V(int semid, int index)             //index = 0
{
    struct sembuf buf = {0, +1, IPC_CREAT};  //信号在信号集中的索引->0   
                                             //操作类型->+1   信号加上+1即+1
                                             //操作标志->IPC_CREAT

    if(index < 0)                            
    {
        perror("index of array cannot equals a minus value!\n");
        return -1;
    }

    buf.sem_num = index;                   //信号在信号集中的索引->0
    if(semop(semid,&buf, 1) == -1)         //信号+1
    {
        perror("a wrong operation to semaphore occurred!");
        return -1;
    }

    return 0;
}

信号集的控制
使用信号集时需要对信号集进行一些控制,例如:删除一个信号集,对semid_ds进行设置,获取信号集中信号量等;这些操作都需要函数semctl进行,该函数的原型为:

#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd,...);

semid为信号集标识符,semnum标识一个特定信号,cmd为操作类型,‘…'表示参数是可选的。他依赖于cmd,通过共用体来选取参数。

int                val;      
struct semid_ds    *buf;
ushort             *array;
struct seminfo     *buf;
void               *pad;

前面说过信号量一般和其他通信类型共同使用,所以我们吧它和共享内存结合使用来理解。

共享内存

共享内存是分配一块能被其他进程访问的内存。同时美国共享内存都有一个shmid_ds结构.

struct shmid_ds{
	struct ipc_perm shm_perm;/* 操作权限*/
    int shm_segsz;                    /*段的大小(以字节为单位)*/
	__kernel_time_t shm_atime;          /*最后一个进程附加到该段的时间*/
    __kernel_time_t shm_dtime;          /*最后一个进程离开该段的时间*/
    __kernel_time_t shm_ctime;          /*最后一个进程修改该段的时间*/
    unsigned short shm_cpid;   /*创建该段进程的pid*/
    unsigned short shm_lpid;   /*在该段上操作的最后1个进程的pid*/
    short shm_nattch;          /*当前附加到该段的进程的个数*/
/*下面是私有的*/
    unsigned short shm_npages;  /*段的大小(以页为单位)*/
    unsigned long *shm_pages;   /*指向frames->SHMMAX的指针数组*/
};

共享内存区的创建
使用shmget函数来创建一个共享内存区,或访问一个已经存在的共享内存区,该函数的函数原型为:

#include <linux/shm.h>
int shmget(key_t key, size_t size, int shmflg);

参数key为键值,size为以字节为单位指定内存的大小,shmflg为操作标志位,它的取值有:

  • IPC_CREATE:如果已经有了相同键值的共享内存则打开已经存在的共享内存,否则创建一个新的共享内存区。
  • IPC_EXCL:与 IPC_CREATE一起使用,否则无意义,如果该键值已创建共享内存区则出错返回-1.
    共享内存区的操作
    在使用共享内存区前,必须通过shmat函数将其附加到进程的地址空间。进程与共享内存就建立了联系。调用成功后就会返回一个指向共享内存区指针,使用该指针访问共享内存区。函数原型为:
#include <linux/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);

参数shmid为shmget的返回值,为贡献内存区的描述符;shmflg为存取权限;shmaddr为共享内存的附加点,取值可为:
在这里插入图片描述
在使用完共享内存区时需要通过函数shmdt断开与共享内存区的链接。该函数的原型为:

#include <sys/shm.h>
int shmdt(const void *shmadder);

参数shmadder为函数shmat的返回值,调用成功返回0,失败返回-1.
共享内存区的控制
对贡献内存区的控制是使用函数shmctl实现的,该函数的原型为:

#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

shmid为标识符;buf为指向shmid_ds结构体的指针,cmd为操作位:
在这里插入图片描述
例:
sharemem.h

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <errno.h>

#define SHM_SIZE 1024

union semun{
    int               val;
    struct semid_ds   *buf;
    unsigned short    *array;
};

//创建信号量函数
int createsem(const char *pathname, int proj_id, int members, int init_val)
{
    key_t       msgkey;            //键值
    int         index,sid;
    union semun semopts;

    if((msgkey = ftok(pathname,proj_id)) == -1)     //获取键值
    {
        perror("ftok error!\n");
        return -1;
    }

    if((sid = semget(msgkey, members, IPC_CREAT|0666)) == -1)    //创建或打开信号集
    {
        perror("semget call failed!\n");
        return -1;
    }

    semopts.val = init_val;
    for(index = 0; index < members; index++)
    {
        semctl(sid, index, SETVAL, semopts);
    }
    return (sid);
}

//P操作函数   信号-1
int sem_P(int semid, int index)             //index = 0
{
    struct sembuf buf = {0, -1, IPC_CREAT};  //信号在信号集中的索引->0   
                                             //操作类型->-1   信号加上-1即-1
                                             //操作标志->IPC_CREAT

    if(index < 0)
    {
        perror("index of array cannot equals a minus value!");
        return -1;
    }

    buf.sem_num = index;                    //信号在信号集中的索引->0
    if(semop(semid , &buf, 1) == -1)        //信号-1
    {
        perror("a wrong operation to semaphore occurred!");
        return -1;
    }
    return 0;
}

//V操作函数   信号+1
int sem_V(int semid, int index)             //index = 0
{
    struct sembuf buf = {0, +1, IPC_CREAT};  //信号在信号集中的索引->0   
                                             //操作类型->+1   信号加上+1即+1
                                             //操作标志->IPC_CREAT

    if(index < 0)                            
    {
        perror("index of array cannot equals a minus value!\n");
        return -1;
    }

    buf.sem_num = index;                   //信号在信号集中的索引->0
    if(semop(semid,&buf, 1) == -1)         //信号+1
    {
        perror("a wrong operation to semaphore occurred!");
        return -1;
    }

    return 0;
}

//等待信号为1
int wait_sem(int semid, int index)            //index = 0
{
    while(semctl(semid, index, GETVAL,0) == 0)
    {
        sleep(1);
    } 

    return 1;
}

//创建共享内存函数
int createshm(char *pathname, int proj_id, size_t size)
{
    key_t       msgkey;            //键值
    int         sid;

    if((msgkey = ftok(pathname, proj_id)) == -1)    //获取键值
    {
        perror("ftok error!\n");
        return -1;
    }

    if((sid = shmget(msgkey, size, IPC_CREAT|0666)) == -1)   //创建或访问共享内存区
    {
        perror("shmget call failed!\n");
        return -1;
    }

    return sid;
}

//删除信号集函数
int sem_deleta(int semid)
{
    return (semctl(semid,0,IPC_RMID));
}

//打开信号量函数
int opensem(const char *pathname, int proj_id)
{
    key_t       msgkey;
    int         sid;

    if((msgkey = ftok(pathname, proj_id)) == -1)      //获取键值
    {
        perror("ftok error!\n");
        return -1;
    }

    if((sid = semget(msgkey,0, IPC_CREAT|0666)) == -1)  //创建或打开信号集
    {
        perror("semget call failed!\n");
        return -1;
    }

    return (sid);
}

reader.c

/*************************************************
 * 先运行writer.c文件,在writer:后输入内容到共享内存区
 * 再运行reader.c文件,读出共享内存区数据
 * **********************************************/
#include "sharemem.h"

int main()
{
    int       semid,shmid;
    char      *shmaddr;

    if((shmid = createshm(".",'m',SHM_SIZE)) == -1)      //打开共享内存区
    {
        exit(1);
    }

    if((shmaddr = shmat(shmid,(char *)0,0)) == (char *)-1)   //返回指向共享内存区的指针
    {
        perror("attach shared memory error!\n");
        exit(1);
    }

    if((semid = opensem(".",'s')) == -1)    //打开信号集
    {
        exit(1);
    }

    while(1)
    {
        printf("reader: ");
        wait_sem(semid, 0);         //等待信号为1
        sem_P(semid, 0);            //信号-1

        printf("%s\n",shmaddr);
        sleep(10);

        sem_V(semid, 0);            //信号+1
        sleep(10);
    }
}

writer.c

/*************************************************
 * 先运行writer.c文件,在writer:后输入内容到共享内存区
 * 再运行reader.c文件,读出共享内存区数据
 * **********************************************/
#include "sharemem.h"

int main()
{
    int       semid,shmid;                      //semget和shmget的返回值
    char      *shmaddr;                         //shmat的返回值
    char      write_str[SHM_SIZE];

    if((shmid = createshm(".",'m', SHM_SIZE)) == -1)          //创建共享内存区
    {
        exit(1);
    }

    if((shmaddr = shmat(shmid, (char *)0, 0)) == (char *)-1)  //返回指向共享内存区的指针
    {
        perror("attach shared memory error!\n");
        exit(1);
    }

    if((semid = createsem(".",'s',1,1)) == -1)                //创建(打开)信号集
    {
        exit(1);
    }

    while(1)
    {
        wait_sem(semid, 0);         //等待信号为1
        sem_P(semid, 0);            //信号-1

        printf("writer: ");
        fgets(write_str,1024,stdin);        //输入内容
        int len = strlen(write_str)-1;
        write_str[len] = '\0';
        strcpy(shmaddr,write_str);          //写入共享内存区
        sleep(10);

        sem_V(semid, 0);                    //信号+1
        sleep(10);
    }
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值