系统编程--进程间通讯

2019年11月07日 阅读数:34
这篇文章主要向大家介绍系统编程--进程间通讯,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

  这篇进程间通讯,包含的内容比较多,包括最基本的pipe和fifo,XSI(System V)标准和POSIX标准的消息队列、信号量、共享内存,同时也有介绍mmap映射的相关内容,算是一个大总结,参考比较多的资料。算法

 

管道
管道是UNIX系统IPC的最古老形式,在shell下的表现形式为管道线。每当在管道线中输入一个由shell执行的命令序列时,shell为每一条命令单首创建一进程,而后将前一条命令进程的标准输出用管道与后一条命令的标准输入相链接。管道有两个主要局限:
1).管道是半双工的,即数据只能在一个方向上流动。
2).管道只能在具备公共祖先的进程之间使用。
管道是由调用pipe函数而建立的.shell

#include <unistd.h>
int pipe(int filedes[2]);
//成功返回0,错误返回-1。

经由参数filedes返回两个文件描述符:filedes[0]为读而打开,filedes[1]为写而打开。filedes[1]的输出是filedes[0]的输入。单个进程中的管道几乎没有任何用处。一般,调用pipe的进程接着调用fork,这样就建立了从父进程到子进程或反之的IPC通道。下面显示了这种状况数组

当管道的一端被关闭后,下列规则起做用:
(1) 当读一个写端已被关闭的管道时,在全部数据都被读取后,read返回0,以指示达到了文件结束处
(2) 若是写一个读端已被关闭的管道,则产生信号SIGPIPE
 
 
pipe(创建管道)
1) 头文件 #include<unistd.h>
2) 定义函数: int pipe(int filedes[2]);
3) 函数说明: pipe()会创建管道,并将文件描述词由参数filedes数组返回。
filedes[0]为管道里的读取端
filedes[1]则为管道的写入端。
4) 返回值: 若成功则返回零,不然返回-1,错误缘由存于errno中。
 
错误代码:
EMFILE 进程已用完文件描述词最大量
ENFILE 系统已无文件描述词可用。
EFAULT 参数 filedes 数组地址不合法。
#include <unistd.h>
#include <stdio.h>

int main(){

    int filedes[2];  
    char buf[80];  
    pid_t pid;  

    pipe( filedes );  
    pid=fork();          
    if (pid > 0)  
    {  
        printf( "This is in the father process,here write a string to the pipe.\n" );  
        char s[] = "Hello world , this is write by pipe.\n";  
        write( filedes[1], s, sizeof(s) );  
        close( filedes[0] );  
        close( filedes[1] );  
    }  
    else if(pid == 0)  
    {  
        printf( "This is in the child process,here read a string from the pipe.\n" );  
        read( filedes[0], buf, sizeof(buf) );  
        printf( "%s\n", buf );  
        close( filedes[0] );  
        close( filedes[1] );  
    }  

    waitpid( pid, NULL, 0 );  
    return 0;
}
使用管道有一些限制:
  两个进程经过一个管道只能实现单向通讯,好比上面的例子,父进程写子进程读,若是有时候也须要子进程写父进程读,就必须另开一个管道。请读者思考,若是只开一个管道,可是父进程不关闭读端,子进程也不关闭写端,双方都有读端和写端,为何不能实现双向通讯?
管道的读写端经过打开的文件描述符来传递,所以要通讯的两个进程必须从它们的公共祖先那里继承管道文件描述符。上面的例子是父进程把文件描述符传给子进程以后父子进程之间通讯,也能够父进程fork两次,把文件描述符传给两个子进程,而后两个子进程之间通讯,总之须要经过fork传递文件描述符使两个进程都能访问同一管道,它们才能通讯。使用管道须要注意如下4种特殊状况(假设都是阻塞I/O操做,没有设置O_NONBLOCK标志):
  1.若是全部指向管道写端的文件描述符都关闭了(管道写端的引用计数等于0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾同样。
  2.若是有指向管道写端的文件描述符没关闭(管道写端的引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。
  3.若是全部指向管道读端的文件描述符都关闭了(管道读端的引用计数等于0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,一般会致使进程异常终止。讲信号时会讲到怎样使SIGPIPE信号不终止进程。
  4.若是有指向管道读端的文件描述符没关闭(管道读端的引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。
  非阻塞管道, fcntl函数设置O_NONBLOCK标志fpathconf(int fd, int name)测试管道缓冲区大小,_PC_PIPE_BUF
 
 
 
FIFO的打开规则
  若是当前打开操做是为读而打开FIFO时,若已经有相应进程为写而打开该FIFO,则当前打开操做将成功返回;不然,可能阻塞直到有相应进程为写而打开该FIFO(当前打开操做设置了阻塞标志);或者,成功返回(当前打开操做没有设置阻塞标志)。
  若是当前打开操做是为写而打开FIFO时,若是已经有相应进程为读而打开该FIFO,则当前打开操做将成功返回;不然,可能阻塞直到有相应进程为读而打开该FIFO(当前打开操做设置了阻塞标志);或者,返回ENXIO错误(当前打开操做没有设置阻塞标志)。
  总之就是一句话,一旦设置了阻塞标志,调用mkfifo创建好以后,那么管道的两端读写必须分别打开,有任何一方未打开,则在调用open的时候就阻塞。
 
Read
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>

#define FIFO_NAME "./my_fifo"  
#define BUFFER_SIZE 20  

int main()
{
    int pipe_fd;
    int res;

    int open_mode = O_RDONLY;
    char buffer[BUFFER_SIZE + 1];

    memset(buffer, '\0', sizeof(buffer));

    printf("Process %d opeining FIFO O_RDONLY\n", getpid());
    pipe_fd = open(FIFO_NAME, open_mode);
    printf("Process %d result %d\n", getpid(), pipe_fd);

    if (pipe_fd != -1)
    {
        do{
            res = read(pipe_fd, buffer, BUFFER_SIZE);
            printf("%s\n",buffer);
        }while(res > 0);
        close(pipe_fd);
    }
    else
    {
        exit(EXIT_FAILURE);
    }

    printf("Process %d finished \n", getpid());
    exit(EXIT_SUCCESS);
}

write缓存

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>

#define FIFO_NAME "./my_fifo"  
#define BUFFER_SIZE 20  

int main()
{
    int pipe_fd;
    int res;
    int open_mode = O_WRONLY;

    char buffer[BUFFER_SIZE + 1];

    if (access(FIFO_NAME, F_OK) == -1)
    {
        res = mkfifo(FIFO_NAME, 0777);
        if (res != 0)
        {
            fprintf(stderr, "Could not create fifo %s\n", FIFO_NAME);
            exit(EXIT_FAILURE);
        }
    }

    printf("Process %d opening FIFO O_WRONLY\n", getpid());
    pipe_fd = open(FIFO_NAME, open_mode);
    printf("Process %d result %d\n", getpid(), pipe_fd);

    sleep(2);
    if (pipe_fd != -1)
    {
        while (1)
        {
            memset(buffer,0,sizeof(buffer));
            scanf("%s",buffer);
            res = write(pipe_fd, buffer, sizeof(buffer));
            if (res == -1)
            {
                fprintf(stderr, "Write error on pipe\n");
                exit(EXIT_FAILURE);
            }
        }
        close(pipe_fd);
    }
    else
    {
        exit(EXIT_FAILURE);
    }

    printf("Process %d finish\n", getpid());
    exit(EXIT_SUCCESS);
}

XSI IPC服务器

XSI IPC特色
1).标示符和键
每一个内核中的IPC结构都用一个非负整数的标识符加以引用。标识符是IPC对象的内部名。为使多个合做进程可以在同一IPC对象上会合,须要提供一个外部命名方案。为此使用键与每一个IPC对象关联。 键的数据类型为key_t,由内核变换成标识符。
ftok提供的惟一服务是从一个路径名和工程ID产生一个关键字的一个方法。
#include <sys/ipc.h>
key_t ftok(const char *path, int id);
//成功返回关键字,失败返回(key_t)-1。

path参数必须指向一个已有的文件。在产生关键字时只有id的低8位被使用。数据结构

2).权限结构
XSI IPC为每个IPC结构设置了一个ipc_perm结构,规定了权限和全部者。它至少包括下列成员:
struct ipc_perm {
    uid_t uid; /* owner's effective user id */
    gid_t gid; /* owner's effective group id */
    uid_t cuid; /* creator's effective user id */
    gid_t cgid; /* creator's effective group id */
    mode_t mode; /* access modes */
    ...
};

能够调用msgctl、semctl或shmctl函数修改uid、gid和mode字段。为了改变这些值,调用进程必须是IPC结构的建立者或超级用户。对于任何IPC结构不存在执行权限。异步

 
XSI--消息队列

消息队列是消息的连接表,存放在内核中并由消息队列标识符标识.。咱们将称消息队列为“队列”,其标识符为“队列ID”
1).每一个队列都有一个msqid_ds结构与其相关。此结构规定了队列的当前状态。函数

struct msqid_ds {
  struct       ipc_perm msg_perm;    /* see Section 15.6.2 */
  msgqnum_t    msg_qnum;             /* # of messages on queue */
  msglen_t     msg_qbytes;           /* max # of bytes on queue */
  pid_t        msg_lspid;            /* pid of last msgsnd() */
  pid_t        msg_lrpid;            /* pid of last msgrcv() */
  time_t       msg_stime;            /* last-msgsnd() time */
  time_t       msg_rtime;            /* last-msgrcv() time */
  time_t      msg_ctime;             /* last-change time */
  ...
};
这个结构体定义了队列的当前状态
2).一般第一个被调用的函数是msgget,其功能是打开一个现存队列或建立一个新队列
#include <sys/msg.h>
int msgget (key_t key, int flag);
//成功返回消息队列ID。错误返回-1。
  程序必须提供一个键来命名某个特定的消息队列。msgflg是一个权限标志,表示消息队列的访问权限,它与文件的访问权限同样。msgflg能够与IPC_CREAT作或操做,表示当key所命名的消息队列不存在时建立一个消息队列,若是key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而只返回一个标识符
3).msgctl函数对队列执行多种操做。它以及另外两个与信号量和共享存储有关的函数(semctl和shmctl)是系统V IPC的相似于ioctl的函数
#include <sys/msg.h>
int msgctl (int msqid, int cmd, struct msqid_ds *buf);
//成功返回0,错误返回-1。

cmd参数指定对于由msqid规定的队列要执行的命令
IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆盖msgid_ds的值。
IPC_SET:若是进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给出的值
IPC_RMID:删除消息队列post

4).调用msgsnd将数据放到消息队列上
#include <sys/msg.h>
int msgsnd(int msqid, const void *ptr, size_t nbytes, int flag);
//成功返回0,错误返回-1。
ptr是一个指向准备发送消息的指针,可是消息的数据结构却有必定的要求,指针msg_ptr所指向的消息结构必定要是以一个长整型成员变量开始的结构体,接收函数将用这个成员来肯定消息的类型。
5).msgrcv从队列中取用消息
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *ptr, size_t nbytes, long type, int flag);
//成功返回消息的数据部分的尺寸,错误返回-1。
type能够实现一种简单的接收优先级。若是msgtype为0,就获取队列中的第一个消息。若是它的值大于零,将获取具备相同消息类型的第一个信息。若是它小于零,就获取类型等于或小于msgtype的绝对值的第一个消息。
msgflg用于控制当队列中没有相应类型的消息能够接收时将发生的事情。
调用成功时,该函数返回放到接收缓存区中的字节数,消息被复制到由msg_ptr指向的用户分配的缓存区中,而后删除消息队列中的对应消息。失败时返回-1.
 
接收
#include <unistd.h>
#include <sys/types.h>
#include <error.h>
#include <sys/msg.h>
#include <errno.h>

#include <stdio.h>
#include <stdlib.h>

#define BUFFSIZ 512

struct msg_st{
    long int msg_type;
    char text[BUFFSIZ];
};
int main()
{
    int running = 1;
    int msgid = -1;
    struct msg_st data;
    long int msgtype = 0; //注意1  

    //创建消息队列  
    msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
    if(msgid == -1)
    {
        fprintf(stderr, "msgget failed with error: %d\n", errno);
        exit(EXIT_FAILURE);
    }
    //从队列中获取消息,直到遇到end消息为止  
    while(running)
    {
        if(msgrcv(msgid, (void*)&data, BUFSIZ, msgtype, 0) == -1)
        {
            fprintf(stderr, "msgrcv failed with errno: %d\n", errno);
            exit(EXIT_FAILURE);
        }
        printf("You wrote: %s\n",data.text);
        //遇到end结束  
        if(strncmp(data.text, "end", 3) == 0)
            running = 0;
    }
    //删除消息队列  
    if(msgctl(msgid, IPC_RMID, 0) == -1)
    {
        fprintf(stderr, "msgctl(IPC_RMID) failed\n");
        exit(EXIT_FAILURE);
    }
    exit(EXIT_SUCCESS);
}

发送测试

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/msg.h>
#include <errno.h>

#define MAX_TEXT 512  
struct msg_st
{   
    long int msg_type;  
    char text[MAX_TEXT];
};

int main()
{
    int running = 1;
    struct msg_st data;
    char buffer[BUFSIZ];
    int msgid = -1;

    //创建消息队列  
    msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
    if(msgid == -1)
    {
        fprintf(stderr, "msgget failed with error: %d\n", errno);
        exit(EXIT_FAILURE);
    }

    //向消息队列中写消息,直到写入end  
    while(running)
    {
        //输入数据  
        printf("Enter some text: ");
        fgets(buffer, BUFSIZ, stdin);
        data.msg_type = 1;    //注意2  
        strcpy(data.text, buffer);
        //向队列发送数据  
        if(msgsnd(msgid, (void*)&data, MAX_TEXT, 0) == -1)
        {
            fprintf(stderr, "msgsnd failed\n");
            exit(EXIT_FAILURE);
        }
        //输入end结束输入  
        if(strncmp(buffer, "end", 3) == 0)
            running = 0;
        sleep(1);
    }
    exit(EXIT_SUCCESS);
}

 

XSI--信号量

信号量与已经介绍过的IPC机构(管道、FIFO以及消息列队)不一样。它是一个计数器,用于多进程对共享数据对象的存取。为了得到共享资源,进程须要执行下列操做:
(1) 测试控制该资源的信号量。
(2) 若此信号量的值为正,则进程可使用该资源。进程将信号量值减1,表示它使用了一个资源单位。
(3) 若此信号量的值为0,则进程进入睡眠状态,直至信号量值大于0。若进程被唤醒后,它返回至(第(1)步)。
内核为每一个信号量设置了一个semid_ds结构:
struct semid_ds {
  struct  ipc_perm  sem_perm;    /* see Section 15.6.2 */
  unsigned short  sem_nsems;     /*  # of semaphores in set */
  time_t  sem_otime;                  /* last-semop() time */
  time_t  sem_ctime;                   /* last-change time */
  ...
};
每一个信号量都表示了一个匿名结构体,包含至少如下成员:
struct {
  unsigned short  semval;    /* semaphore value, always >= 0 */
  pid_t           sempid;    /* pid for last operation */
  unsigned short semncnt;    /* # processes awaiting semval>curval */
  unsigned short  semzcnt;   /* # processes awaiting semval==0 */
};
semget函数
它的做用是建立一个新信号量或取得一个已有信号量,原型为
int semget(key_t key, int num_sems, int sem_flags);
  第一个参数key是整数值(惟一非零),不相关的进程能够经过它访问一个信号量,它表明程序可能要使用的某个资源,程序对全部信号量的访问都是间接的,程序先经过调用semget函数并提供一个键,再由系统生成一个相应的信号标识符(semget函数的返回值),只有semget函数才直接使用信号量键,全部其余的信号量函数使用由semget函数返回的信号量标识符。若是多个程序使用相同的key值,key将负责协调工做。
  第二个参数num_sems指定须要的信号量数目,它的值几乎老是1。
  第三个参数sem_flags是一组标志,当想要当信号量不存在时建立一个新的信号量,能够和值IPC_CREAT作按位或操做。设置了IPC_CREAT标志后,即便给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则能够建立一个新的,惟一的信号量,若是信号量已存在,返回一个错误。  
  semget函数成功返回一个相应信号标识符(非零),失败返回-1.
 
semop函数
它的做用是改变信号量的值,原型为:
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops); 
sem_id是由semget返回的信号量标识符
 
semctl函数
该函数用来直接控制信号量信息,它的原型为:
int semctl(int sem_id, int sem_num, int command, ...); 
前两个参数与前面一个函数中的同样,command一般是下面两个值中的其中一个
SETVAL:用来把信号量初始化为一个已知的值。
IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。
 
PV操做的测试
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/sem.h>

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

static int sem_id = 0;

static int set_semvalue();
static void del_semvalue();
static int semaphore_p();
static int semaphore_v();

int main(int argc, char *argv[])
{
    char message = 'X';
    int i = 0;

    //建立信号量
    sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);

    if(argc > 1)
    {
        //程序第一次被调用,初始化信号量
        if(!set_semvalue())
        {
            fprintf(stderr, "Failed to initialize semaphore\n");
            exit(EXIT_FAILURE);
        }
        //设置要输出到屏幕中的信息,即其参数的第一个字符
        message = argv[1][0];
        sleep(2);
    }
    for(i = 0; i < 10; ++i)
    {
        //进入临界区
        if(!semaphore_p())
            exit(EXIT_FAILURE);
        //向屏幕中输出数据
        printf("%c", message);
        //清理缓冲区,而后休眠随机时间
        fflush(stdout);
        sleep(rand() % 3);
        //离开临界区前再一次向屏幕输出数据
        printf("%c", message);
        fflush(stdout);
        //离开临界区,休眠随机时间后继续循环
        if(!semaphore_v())
            exit(EXIT_FAILURE);
        sleep(rand() % 2);
    }

    sleep(10);
    printf("\n%d - finished\n", getpid());

    if(argc > 1)
    {
        //若是程序是第一次被调用,则在退出前删除信号量
        sleep(3);
        del_semvalue();
    }
    exit(EXIT_SUCCESS);
}

static int set_semvalue()
{
    //用于初始化信号量,在使用信号量前必须这样作
    union semun sem_union;

    sem_union.val = 1;
    if(semctl(sem_id, 0, SETVAL, sem_union) == -1)
        return 0;
    return 1;
}

static void del_semvalue()
{
    //删除信号量
    union semun sem_union;

    if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
        fprintf(stderr, "Failed to delete semaphore\n");
}

static int semaphore_p()
{
    //对信号量作减1操做,即等待P(sv)
    struct sembuf sem_b;
    sem_b.sem_num = 0;
    sem_b.sem_op = -1;//P()
    sem_b.sem_flg = SEM_UNDO;
    if(semop(sem_id, &sem_b, 1) == -1)
    {
        fprintf(stderr, "semaphore_p failed\n");
        return 0;
    }
    return 1;
}

static int semaphore_v()
{
    //这是一个释放操做,它使信号量变为可用,即发送信号V(sv)
    struct sembuf sem_b;
    sem_b.sem_num = 0;
    sem_b.sem_op = 1;//V()
    sem_b.sem_flg = SEM_UNDO;
    if(semop(sem_id, &sem_b, 1) == -1)
    {
        fprintf(stderr, "semaphore_v failed\n");
        return 0;
    }
    return 1;
}

 

XSI--共享内存
  共享存储容许两个或多个进程共享一给定的存储区。由于数据不须要在客户机和服务器之间复制,因此这是最快的一种IPC。使用共享存储的惟一窍门是多个进程之间对一给定存储区的同步存取。
内核为每一个共享存储段设置了一个shmidds结构。
struct shmid_ds {
  struct ipc_perm  shm_perm;      /* see Section 15.6.2 */
  size_t           shm_segsz;     /* size of segment in bytes */
  pid_t            shm_lpid;      /* pid of last shmop() */
  pid_t            shm_cpid;      /* pid of creator */
  shmatt_t         shm_nattch;    /* number of current attaches */
  time_t           shm_atime;     /* last-attach time */
  time_t           shm_dtime;     /* last-detach time */
  time_t           shm_ctime;     /* last-change time */
  ...
};
shmget函数
该函数用来建立共享内存,它的原型为:
int shmget(key_t key, size_t size, int shmflg);  
  第一个参数,与信号量的semget函数同样,程序须要提供一个参数key(非0整数),它有效地为共享内存段命名,shmget函数成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1.不相关的进程能够经过该函数的返回值访问同一共享内存,它表明程序可能要使用的某个资源,程序对全部共享内存的访问都是间接的,程序先经过调用shmget函数并提供一个键,再由系统生成一个相应的共享内存标识符(shmget函数的返回值),只有shmget函数才直接使用信号量键,全部其余的信号量函数使用由semget函数返回的信号量标识符。
  第二个参数,size以字节为单位指定须要共享的内存容量
  第三个参数,shmflg是权限标志,它的做用与open函数的mode参数同样,若是要想在key标识的共享内存不存在时,建立它的话,能够与IPC_CREAT作或操做。共享内存的权限标志与文件的读写权限同样,
 
shmat函数
第一次建立完共享内存时,它还不能被任何进程访问,shmat函数的做用就是用来启动对该共享内存的访问,并把共享内存链接到当前进程的地址空间。它的原型以下:
void *shmat(int shm_id, const void *shm_addr, int shmflg);  
  第一个参数,shm_id是由shmget函数返回的共享内存标识。
  第二个参数,shm_addr指定共享内存链接到当前进程中的地址位置,一般为空,表示让系统来选择共享内存的地址。
  第三个参数,shm_flg是一组标志位,一般为0。
  调用成功时返回一个指向共享内存第一个字节的指针,若是调用失败返回-1.
 
shmdt函数
该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并非删除它,只是使该共享内存对当前进程再也不可用。它的原型以下:
int shmdt(const void *shmaddr); 
  参数shmaddr是shmat函数返回的地址指针,调用成功时返回0,失败时返回-1.
 
shmctl函数
与信号量的semctl函数同样,用来控制共享内存,它的原型以下:
int shmctl(int shm_id, int command, struct shmid_ds *buf);  
  第一个参数,shm_id是shmget函数返回的共享内存标识符。 
  第二个参数,command是要采起的操做,它能够取下面的三个值 :
      IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。
      IPC_SET:若是进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
      IPC_RMID:删除共享内存段
  第三个参数,buf是一个结构指针,它指向共享内存模式和访问权限的结构。
 
写测试(读相似)
#include <unistd.h>
#include <error.h>
#include <sys/types.h>
#include <sys/shm.h>

#define SHM_SIZE 1024
#define SHM_MODE (SHM_R|SHM_W|IPC_CREAT)

int main(){
    void *shm=NULL;
    int *shared=NULL;
    int shmid=shmget((key_t)23,sizeof(int),SHM_MODE);
    if(shmid==-1){
        perror("shmget error");
    }
    shm=shmat(shmid,(void*)0,0);
    if(shm==(void*)-1){
        perror("shmat error");
    }
    shared=(int *)shm;

    int i=0;
    while(1){
        sleep(1);
        *shared=i++;
    }

    if(shmdt(shm)==-1){
        perror("shmdt error");
    }
    return 0;

POSIX IPC

使用gcc编译时,须要加上 -lrt

POSIX IPC名字标准:

一个IPC名字,它多是某个文件系统中的一个真正存在的路径名,也可能不是。Posix.1是这样描述Posix IPC名字的。
1)它必须符合已有的路径名规则(最多由PATH_MAX个字节构成,包括结尾的空字节)
2)若是它以斜杠开头,那么对这些函数的不一样调用将访问同一个队列,不然效果取决于实现(也就是效果没有标准化)
3)名字中的额外的斜杠符的解释由实现定义(一样是没有标准化) 所以,为便于移植起见,Posix IPC名字必须以一个斜杠打头,而且不能再包含任何其余斜杠符。
 
 
POSIX--消息队列
POSIX消息队列的建立,关闭和删除用到如下三个函数接口:
#include <mqueue.h>  
mqd_t mq_open(const char *name, int oflag, /* mode_t mode, struct mq_attr *attr */);  
                       //成功返回消息队列描述符,失败返回-1  
mqd_t mq_close(mqd_t mqdes);  
mqd_t mq_unlink(const char *name);  
                           //成功返回0,失败返回-1  
mq_open用于打开或建立一个消息队列。
name:表示消息队列的名字,它符合POSIX IPC的名字规则。
oflag:表示打开的方式,和open函数的相似。有必须的项:O_RDONLY,O_WRONLY,O_RDWR,还有可选的选项:O_NONBLOCK,O_CREAT,O_EXCL。
mode:是一个可选参数,在oflag中含有O_CREAT标志且消息队列不存在时,才须要提供该参数。表示默认访问权限。能够参考open。
attr:也是一个可选参数,在oflag中含有O_CREAT标志且消息队列不存在时才须要。该参数用于给新队列设定某些属性,若是是空指针,那么就采用默认属性。
 
mq_close用于关闭一个消息队列,和文件的close类型,关闭后,消息队列并不从系统中删除。一个进程结束,会自动调用关闭打开着的消息队列。
mq_unlink用于删除一个消息队列。消息队列建立后只有经过调用该函数或者是内核自举才能进行删除。每一个消息队列都有一个保存当前打开着描述符数的引用计数器,和文件同样,所以本函数可以实现相似于unlink函数删除一个文件的机制。
 
消息队列属性
POSIX标准规定消息队列属性mq_attr必需要含有如下四个内容:
long    mq_flags //消息队列的标志:0或O_NONBLOCK,用来表示是否阻塞 
long    mq_maxmsg  //消息队列的最大消息数
long    mq_msgsize  //消息队列中每一个消息的最大字节数
long    mq_curmsgs  //消息队列中当前的消息数目


mq_attr结构的定义以下:
#include <mqueue.h>
mqd_t mq_getattr(mqd_t mqdes, struct mq_attr *attr);
mqd_t mq_setattr(mqd_t mqdes, struct mq_attr *newattr, struct mq_attr *oldattr);
                               //成功返回0,失败返回-1
mq_getattr用于获取当前消息队列的属性,mq_setattr用于设置当前消息队列的属性。其中mq_setattr中的oldattr用于保存修改前的消息队列的属性,能够为空。
mq_setattr能够设置的属性只有mq_flags,用来设置或清除消息队列的非阻塞标志。newattr结构的其余属性被忽略。mq_maxmsg和mq_msgsize属性只能在建立消息队列时经过mq_open来设置。mq_open只会设置该两个属性,忽略另外两个属性。mq_curmsgs属性只能被获取而不能被设置。
#include <unistd.h>
#include <sys/types.h>
#include <error.h>
#include <fcntl.h>
#include <mqueue.h>
#include <sys/stat.h>

#include <stdio.h>
#include <stdlib.h>

int main()
{
    mqd_t mqid;
    mqid=mq_open("/myqueue",O_RDWR|O_CREAT,0666,NULL);
    if(mqid<0){
        perror("open error");
        exit(mqid);
    }
    struct mq_attr mqattr;
    if(mq_getattr(mqid,&mqattr)<0){
        perror("get attr error");
        exit(-1);
    }

    printf("mq_flags: %ld\n",mqattr.mq_flags);
    printf("mq_maxmsg: %ld\n",mqattr.mq_maxmsg);
    printf("mq_msgsize: %ld\n",mqattr.mq_msgsize);
    printf("mq_curmsgs: %ld\n",mqattr.mq_curmsgs);

    return 0;
}

 

消息队列使用

POSIX消息队列能够经过如下两个函数来进行发送和接收消息:
#include <mqueue.h>
mqd_t mq_send(mqd_t mqdes, const char *msg_ptr,
                      size_t msg_len, unsigned msg_prio);
                     //成功返回0,出错返回-1

mqd_t mq_receive(mqd_t mqdes, char *msg_ptr,
                      size_t msg_len, unsigned *msg_prio);
                     //成功返回接收到消息的字节数,出错返回-1

#ifdef __USE_XOPEN2K
mqd_t mq_timedsend(mqd_t mqdes, const char *msg_ptr,
                      size_t msg_len, unsigned msg_prio,
                      const struct timespec *abs_timeout);

mqd_t mq_timedreceive(mqd_t mqdes, char *msg_ptr,
                      size_t msg_len, unsigned *msg_prio,
                      const struct timespec *abs_timeout);
#endif
mq_send向消息队列中写入一条消息,mq_receive从消息队列中读取一条消息。
mqdes:消息队列描述符;
msg_ptr:指向消息体缓冲区的指针;
msg_len:消息体的长度 ,其中mq_receive的该参数不能小于能写入队列中消息的最大大小,即必定要大于等于该队列的mq_attr结构中mq_msgsize的大小。若是mq_receive中的msg_len小于该值,就会返回EMSGSIZE错误。POXIS消息队列发送的消息长度能够为0。
msg_prio:消息的优先级;它是一个小于MQ_PRIO_MAX的数,数值越大,优先级越高。POSIX消息队列在调用mq_receive时老是返回队列中最高优先级的最先消息。若是消息不须要设定优先级,那么能够在mq_send是置msg_prio为0,mq_receive的msg_prio置为NULL。
  
  还有两个XSI定义的扩展接口限时发送和接收消息的函数:mq_timedsend和mq_timedreceive函数。默认状况下mq_send和mq_receive是阻塞进行调用,能够经过mq_setattr来设置为O_NONBLOCK。
 
写测试
#include <unistd.h>
#include <sys/types.h>
#include <error.h>
#include <fcntl.h>
#include <mqueue.h>
#include <sys/stat.h>

#include <stdio.h>
#include <stdlib.h>

int main()
{
    mqd_t mqid;
    mqid=mq_open("/myqueue",O_RDWR|O_CREAT,0666,NULL);
    if(mqid<0){
        perror("open error");
        exit(mqid);
    }
    struct mq_attr mqattr;
    if(mq_getattr(mqid,&mqattr)<0){
        perror("get attr error");
        exit(-1);
    }

    int bufsize=mqattr.mq_msgsize;

    char *buf=(char *)malloc(sizeof(char)*bufsize);
    int prio=10;
    int running=1;
    while(running){
        //输入数据  
        printf("Enter some text: ");
        fgets(buf, bufsize, stdin);
        //向队列发送数据  
        if(mq_send(mqid, buf, bufsize, prio) <0)
        {
            fprintf(stderr, "msgsnd failed\n");
            exit(EXIT_FAILURE);
        }
        //输入end结束输入  
        if(strncmp(buf, "end", 3) == 0)
            running = 0;
        sleep(1);
        prio++;
    }
    free(buf);
    mq_close(mqid);
    return 0;
}

读测试

#include <unistd.h>
#include <sys/types.h>
#include <error.h>
#include <fcntl.h>
#include <mqueue.h>
#include <sys/stat.h>

#include <stdio.h>
#include <stdlib.h>

int main()
{
    mqd_t mqid;
    mqid=mq_open("/myqueue",O_RDWR|O_CREAT,0666,NULL);
    if(mqid<0){
        perror("open error");
        exit(mqid);
    }
    struct mq_attr mqattr;
    if(mq_getattr(mqid,&mqattr)<0){
        perror("get attr error");
        exit(-1);
    }

    int bufsize=mqattr.mq_msgsize;

    char *buf=(char *)malloc(sizeof(char)*bufsize);
    int prio=10;
    int running=1;
    while(running){
        if(mq_receive(mqid, buf, bufsize, NULL) <0)
        {
            fprintf(stderr, "msgsnd failed\n");
            exit(EXIT_FAILURE);
        }
        printf("rec: %s\n",buf);
        //输入end结束输入  
        if(strncmp(buf, "end", 3) == 0)
            running = 0;
    }
    free(buf);
    mq_close(mqid);
    return 0;
}

 

 

POSIX--信号量

  POSIX信号量有两种: 有名信号量和无名信号量,无名信号量也被称做基于内存的信号量。有名信号量经过IPC名字进行进程间的同步,而无名信号量若是不是放在进程间的共享内存区中,是不能用来进行进程间同步的,只能用来进行线程同步。
 
POSIX信号量有三种操做:
(1)建立一个信号量。建立的过程还要求初始化信号量的值。
根据信号量取值(表明可用资源的数目)的不一样,POSIX信号量还能够分为:
  • 二值信号量:信号量的值只有0和1,这和互斥量很类型,若资源被锁住,信号量的值为0,若资源可用,则信号量的值为1;
  • 计数信号量:信号量的值在0到一个大于1的限制值(POSIX指出系统的最大限制值至少要为32767)。该计数表示可用的资源的个数。
(2)等待一个信号量(wait)。该操做会检查信号量的值,若是其值小于或等于0,那就阻塞,直到该值变成大于0,而后等待进程将信号量的值减1,进程得到共享资源的访问权限。这整个操做必须是一个原子操做。该操做还常常被称为P操做(荷兰语Proberen,意为:尝试)。
(3)挂出一个信号量(post)。该操做将信号量的值加1,若是有进程阻塞着等待该信号量,那么其中一个进程将被唤醒。该操做也必须是一个原子操做。该操做还常常被称为V操做(荷兰语Verhogen,意为:增长)
 
不少时候信号量和互斥量,条件变量三者均可以在某种应用中使用,那这三者的差别有哪些呢,下面列出了这三者之间的差别:
  • 互斥量必须由给它上锁的线程解锁。而信号量不须要由等待它的线程进行挂出,能够在其余进程进行挂出操做。
  • 互斥量要么被锁住,要么是解开状态,只有这两种状态。而信号量的值能够支持多个进程成功进行wait操做。
  • 信号量的挂出操做老是被记住,由于信号量有一个计数值,挂出操做总会将该计数值加1,然而当向条件变量发送一个信号时,若是没有线程等待在条件变量,那么该信号会丢失。
有名信号量的建立和删除
#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag,
                  mode_t mode, unsigned int value);
                              //成功返回信号量指针,失败返回SEM_FAILED
sem_open用于建立或打开一个信号量,信号量是经过name参数即信号量的名字来进行标识的。
oflag参数能够为:0,O_CREAT,O_EXCL。若是为0表示打开一个已存在的信号量,若是为O_CREAT,表示若是信号量不存在就建立一个信号量,若是存在则打开被返回。此时mode和value须要指定。若是为O_CREAT | O_EXCL,表示若是信号量已存在会返回错误。
mode参数用于建立信号量时,表示信号量的权限位,和open函数同样包括:S_IRUSR,S_IWUSR,S_IRGRP,S_IWGRP,S_IROTH,S_IWOTH。
value表示建立信号量时,信号量的初始值。
#include <semaphore.h>

int sem_close(sem_t *sem);
int sem_unlink(const char *name);
                              //成功返回0,失败返回-1
sem_close用于关闭打开的信号量。当一个进程终止时,内核对其上仍然打开的全部有名信号量自动执行这个操做。调用sem_close关闭信号量并无把它从系统中删除它,POSIX有名信号量是随内核持续的。即便当前没有进程打开某个信号量它的值依然保持。直到内核从新自举或调用sem_unlink()删除该信号量。
sem_unlink用于将有名信号量马上从系统中删除,但信号量的销毁是在全部进程都关闭信号量的时候。
 
信号量的P操做
#include <semaphore.h>
int sem_wait (sem_t *sem);

#ifdef __USE_XOPEN2K
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
#endif

int sem_trywait (sem_t * sem);
                              //成功返回0,失败返回-1
sem_wait()用于获取信号量,首先会测试指定信号量的值,若是大于0,就会将它减1并当即返回,若是等于0,那么调用线程会进入睡眠,指定信号量的值大于0.
sem_trywait和sem_wait的差异是,当信号量的值等于0的,调用线程不会阻塞,直接返回,并标识EAGAIN错误。
sem_timedwait和sem_wait的差异是当信号量的值等于0时,调用线程会限时等待。当等待时间到后,信号量的值仍是0,那么就会返回错误。其中 struct timespec *abs_timeout是一个绝对时间,具体能够参考条件变量关于等待时间的使用
信号量的V操做
#include <semaphore.h>
int sem_post(sem_t *sem);
                            //成功返回0,失败返回-1
当一个线程使用完某个信号量后,调用sem_post,使该信号量的值加1,若是有等待的线程,那么会唤醒等待的一个线程。
 
获取当前信号量的值
#include <semaphore.h>
int sem_getvalue(sem_t *sem,  int *sval);
                            //成功返回0,失败返回-1
该函数返回当前信号量的值,经过sval输出参数返回,若是当前信号量已经上锁(即同步对象不可用),那么返回值为0,或为负数,其绝对值就是等待该信号量解锁的线程数。
 
有名信号量测试
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <semaphore.h>
#include <signal.h>
#include <pthread.h>

#include <stdio.h>
#include <stdlib.h>

#define SEM1_NAME "/mysem1"
#define SEM2_NAME "/mysem2"

sem_t *pSem1;
sem_t *pSem2;
int stopflag=0;
int val1,val2;
static void sigAction(int signo){
    if(signo==SIGINT)
        stopflag=1;
}

void threadfn1(){
    while(stopflag!=1){
        sem_wait(pSem2);
        sleep(1);
        printf("This is thread 1\n");
        if(sem_getvalue(pSem1,&val1)<0)
            perror("get sem val err");
        if(sem_getvalue(pSem2,&val2)<0)
            perror("get sem val err");
        printf("val1= %d\tval2= %d\n",val1,val2);
        sem_post(pSem1);
    }
}
void threadfn2(){
    while(stopflag!=1){
        sem_wait(pSem1);
        sleep(1);
        printf("This is thread 2\n");
        if(sem_getvalue(pSem1,&val1)<0)
            perror("get sem val err");
        if(sem_getvalue(pSem2,&val2)<0)
            perror("get sem val err");
        printf("val1= %d\tval2= %d\n",val1,val2);
        sem_post(pSem2);
    }
}
int main(){
    if(signal(SIGINT,sigAction)==SIG_ERR)
        perror("catch SIGINT err");

    pSem1=sem_open(SEM1_NAME,O_CREAT,0666,1);
    pSem2=sem_open(SEM2_NAME,O_CREAT,0666,1);

    pthread_t tid1,tid2;
    pthread_create(&tid1,NULL,&threadfn1,NULL);
    pthread_create(&tid2,NULL,&threadfn2,NULL);

    sleep(2);
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);

    sem_close(pSem1);
    sem_unlink(SEM1_NAME);
    sem_close(pSem2);
    sem_unlink(SEM2_NAME);

    return 0;
}

 

无名信号量的建立与销毁
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
                            //若出错则返回-1
int sem_destroy(sem_t *sem);
                            //成功返回0,失败返回-1
  sem_init()用于无名信号量的初始化。无名信号量在初始化前必定要在内存中分配一个sem_t信号量类型的对象,这就是无名信号量又称为基于内存的信号量的缘由。
  sem_init()第一个参数是指向一个已经分配的sem_t变量。第二个参数pshared表示该信号量是否因为进程间通步,当pshared = 0,那么表示该信号量只能用于进程内部的线程间的同步。当pshared != 0,表示该信号量存放在共享内存区中,使使用它的进程可以访问该共享内存区进行进程同步。第三个参数value表示信号量的初始值。
  这里须要注意的是, 无名信号量不使用任何相似O_CREAT的标志,这表示sem_init()老是会初始化信号量的值,因此对于特定的一个信号量,咱们必须保证只调用sem_init()进行初始化一次,对于一个已初始化过的信号量调用sem_init()的行为是未定义的。若是信号量尚未被某个线程调用还好,不然基本上会出现问题。
  使用完一个无名信号量后, 调用sem_destroy摧毁它。这里要注意的是:摧毁一个有线程阻塞在其上的信号量的行为是未定义的。
 
无名信号量测试
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <semaphore.h>
#include <signal.h>
#include <pthread.h>

#include <stdio.h>
#include <stdlib.h>

sem_t pSem1;
sem_t pSem2;
int stopflag=0;
int val1,val2;
static void sigAction(int signo){
    if(signo==SIGINT)
        stopflag=1;
}

void threadfn1(){
    while(stopflag!=1){
        sem_wait(&pSem2);
        sleep(1);
        printf("This is thread 1\n");
        if(sem_getvalue(&pSem1,&val1)<0)
            perror("get sem val err");
        if(sem_getvalue(&pSem2,&val2)<0)
            perror("get sem val err");
        printf("val1= %d\tval2= %d\n",val1,val2);
        sem_post(&pSem1);
    }
}

void threadfn2(){
    while(stopflag!=1){
        sem_wait(&pSem1);
        sleep(1);
        printf("This is thread 2\n");
        if(sem_getvalue(&pSem1,&val1)<0)
            perror("get sem val err");
        if(sem_getvalue(&pSem2,&val2)<0)
            perror("get sem val err");
        printf("val1= %d\tval2= %d\n",val1,val2);
        sem_post(&pSem2);
    }
}
int main(){
    if(signal(SIGINT,sigAction)==SIG_ERR)
        perror("catch SIGINT err");

    sem_init(&pSem1,1,1);
    sem_init(&pSem2,1,1);

    pthread_t tid1,tid2;
    pthread_create(&tid1,NULL,&threadfn1,NULL);
    pthread_create(&tid2,NULL,&threadfn2,NULL);

    sleep(2);
    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);

    sem_destroy(&pSem1);
    sem_destroy(&pSem2);


    return 0;
}
有名和无名信号量的持续性
  有名信号量是随内核持续的。当有名信号量建立后,即便当前没有进程打开某个信号量它的值依然保持。直到内核从新自举或调用sem_unlink()删除该信号量。
无名信号量的持续性要根据信号量在内存中的位置:
  • 若是无名信号量是在单个进程内部的数据空间中,即信号量只能在进程内部的各个线程间共享,那么信号量是随进程的持续性,当进程终止时它也就消失了。
  • 若是无名信号量位于不一样进程的共享内存区,所以只要该共享内存区仍然存在,该信号量就会一直存在。因此此时无名信号量是随内核的持续性。

 

POSIX--共享内存

  共享内存也是一种IPC,它是目前可用IPC中最快的,它是使用方式是将同一个内存区映射到共享它的不一样进程的地址空间中,这样这些进程间的通讯就再也不须要经过内核,只需对该共享的内存区域进程操做就能够了,和其余IPC不一样的是,共享内存的使用须要用户本身进行同步操做。
 
mmap系列函数简介
mmap函数主要的功能就是将文件或设备映射到调用进程的地址空间中,当使用mmap映射文件到进程后,就能够直接操做这段虚拟地址进行文件的读写等操做,没必要再调用read,write等系统调用。在很大程度上提升了系统的效率和代码的简洁性。
使用mmap函数的主要目的是:
  • 对普通文件提供内存映射I/O,能够提供无亲缘进程间的通讯;
  • 提供匿名内存映射,以供亲缘进程间进行通讯。
  •  对shm_open建立的POSIX共享内存区对象进程内存映射,以供无亲缘进程间进行通讯。
#include <sys/mman.h>
void *mmap(void *start, size_t len, int prot, int flags, int fd, off_t offset);
               //成功返回映射到进程地址空间的起始地址,失败返回MAP_FAILED
  start:指定描述符fd应被映射到的进程地址空间内的起始地址,它一般被设置为空指针NULL,这告诉内核自动选择起始地址,该函数的返回值即为fd映射到内存区的起始地址。
  len:映射到进程地址空间的字节数,它从被映射文件开头的第offset个字节处开始,offset一般被设置为0。
 

 

 
prot:内存映射区的保护由该参数来设定,一般由如下几个值组合而成:
  • PROT_READ:数据可读;
  •  PROT_WRITE:数据可写;
  •  PROT_EXEC:数据可执行;
  •  PROT_NONE:数据不可访问;
flags:设置内存映射区的类型标志,POSIX标志定义了如下三个标志:
  • MAP_SHARED:该标志表示,调用进程对被映射内存区的数据所作的修改对于共享该内存区的全部进程均可见,并且确实改变其底层的支撑对象(一个文件对象或是一个共享内存区对象)。
  •  MAP_PRIVATE:调用进程对被映射内存区的数据所作的修改只对该进程可见,而不改变其底层支撑对象。
  •  MAP_FIXED:该标志表示准确的解释start参数,通常不建议使用该标志,对于可移植的代码,应该把start参数置为NULL,且不指定MAP_FIXED标志。
fd:有效的文件描述符。若是设定了MAP_ANONYMOUS(MAP_ANON)标志,在Linux下面会忽略fd参数,而有的系统实现如BSD须要置fd为-1;
offset:相对文件的起始偏移。
 
从进程的地址空间中删除一个映射关系,须要用到下面的函数:
#include <sys/mman.h>
int munmap(void *start, size_t len);
                           //成功返回0,出错返回-1
start:被映射到的进程地址空间的内存区的起始地址,即mmap返回的地址。
len:映射区的大小。
对于一个MAP_SHARED的内存映射区,内核的虚拟内存算法会保持内存映射文件和内存映射区的同步,也就是说,对于内存映射文件所对应内存映射区的修改,内核会在稍后的某个时刻更新该内存映射文件。若是咱们但愿硬盘上的文件内容和内存映射区中的内容实时一致,那么咱们就能够调用msync开执行这种同步:
 #include <sys/mman.h>
 int msync(void *start, size_t len, int flags);
                           //成功返回0,出错返回-1
start:被映射到的进程地址空间的内存区的起始地址,即mmap返回的地址。
len:映射区的大小。
flags:同步标志,有一下三个标志:
  • MS_ASYNC:异步写,一旦写操做由内核排入队列,就马上返回;
  • MS_SYNC:同步写,要等到写操做完成后才返回。
  •  MS_INVALIDATE:使该文件的其余内存映射的副本所有失效。
父子进程匿名映射
  即将mmap的flags参数指定为:MAP_SHARED | MAP_ANON。这样就完全避免了内存映射文件的建立和打开,简化了对文件的操做。匿名内存映射机制的目的就是为了提供一个穿越父子进程间的内存映射区
#include <unistd.h>
#include <sys/types.h>
#include <error.h>
#include <fcntl.h>
#include <sys/mman.h>

#include <stdio.h>
#include <stdlib.h>


int main(){

    int *memPtr;
    memPtr=(int*)mmap(NULL,sizeof(int),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,0,0);
    if(memPtr==MAP_FAILED){
        perror("mmap failed");
        exit(-1);
    }
    *memPtr=0;
    if(fork()==0){
        *memPtr=1;
        printf("child -- %d\n",*memPtr);
        exit(0);
    }

    sleep(1);
    printf("father -- %d\n",*memPtr);

    return 0;
}
经过内存映射文件提供无亲缘进程间的通讯
#include <unistd.h>
#include <sys/types.h>
#include <error.h>
#include <fcntl.h>
#include <sys/mman.h>

#include <stdio.h>
#include <stdlib.h>

#define PATH_NAME "/tmp/memmap1"

int main(){
    int fd;
    fd=open(PATH_NAME,O_RDWR|O_CREAT,0666);
    if(fd<0){
        perror("open failed");
        exit(fd);
    }

    if(ftruncate(fd,sizeof(int))<0){
        perror("change file size failed");
        close(fd);
        exit(-1);
    }

    int *memPtr;
    memPtr=(int*)mmap(NULL,sizeof(int),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    close(fd);
    if(memPtr==MAP_FAILED){
        perror("mmap failed");
        exit(-1);
    }
    *memPtr=10;
    printf("write:%d\n",*memPtr);

    return 0;
}
基于mmap的POSIX共享内存

 

上面介绍了经过内存映射文件进行进程间的通讯的方式,如今要介绍的是经过POSIX共享内存区对象进行进程间的通讯。POSIX共享内存使用方法有如下两个步骤:
  • 经过shm_open建立或打开一个POSIX共享内存对象;
  • 而后调用mmap将它映射到当前进程的地址空间;
和经过内存映射文件进行通讯的使用上差异在于mmap描述符参数获取方式不同:经过open或shm_open。以下图所示:
POSIX共享内存区对象的特殊操做函数就只有建立(打开)和删除两个函数,其余对共享内存区对象的操做都是经过已有的函数进行的。
#include <sys/mman.h>
int shm_open(const char *name, int oflag, mode_t mode);
                              //成功返回非负的描述符,失败返回-1
int shm_unlink(const char *name);
                              //成功返回0,失败返回-1
shm_open用于建立一个新的共享内存区对象或打开一个已经存在的共享内存区对象。
name:POSIX IPC的名字,前面关于POSIX进程间通讯都已讲过关于POSIX IPC的规则,这里再也不赘述。
oflag:操做标志,包含:O_RDONLY,O_RDWR,O_CREAT,O_EXCL,O_TRUNC。其中O_RDONLY和O_RDWR标志必须且仅能存在一项。
mode:用于设置建立的共享内存区对象的权限属性。和open以及其余POSIX IPC的xxx_open函数不一样的是,该参数必须一直存在,若是oflag参数中没有O_CREAT标志,该位能够置0;
shm_unlink用于删除一个共享内存区对象,跟其余文件的unlink以及其余POSIX IPC的删除操做同样,对象的析构会到对该对象的全部引用所有关闭才会发生。
POSIX共享内存和POSIX消息队列,有名信号量同样都是具备随内核持续性的特色。
 
写入
#include <unistd.h>
#include <sys/types.h>
#include <error.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>

#include <stdio.h>
#include <stdlib.h>

#define PATH_NAME "/shm"

int main(){
    int fd;
    fd=shm_open(PATH_NAME,O_RDWR|O_CREAT,0666);
    if(fd<0){
        perror("open failed");
        exit(fd);
    }

    if(ftruncate(fd,sizeof(int))<0){
        perror("change file size failed");
        close(fd);
        exit(-1);
    }

    int *memPtr;
    memPtr=(int*)mmap(NULL,sizeof(int),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    close(fd);
    if(memPtr==MAP_FAILED){
        perror("mmap failed");
        exit(-1);
    }
    *memPtr=10;
    printf("write:%d\n",*memPtr);

    return 0;
}

读取

#include <unistd.h>
#include <sys/types.h>
#include <error.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>

#include <stdio.h>
#include <stdlib.h>

#define PATH_NAME "/shm"

int main(){
    int fd;
    fd=shm_open(PATH_NAME,O_RDWR|O_CREAT,0666);
    if(fd<0){
        perror("open failed");
        exit(fd);
    }

    if(ftruncate(fd,sizeof(int))<0){
        perror("change file size failed");
        close(fd);
        exit(-1);
    }

    int *memPtr;
    memPtr=(int*)mmap(NULL,sizeof(int),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    close(fd);
    if(memPtr==MAP_FAILED){
        perror("mmap failed");
        exit(-1);
    }
    printf("read:%d\n",*memPtr);
    shm_unlink(PATH_NAME);
    return 0;
}