系统编程--进程间通信

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

 

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

#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:删除消息队列

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;
}

 

posted @ 2017-03-13 01:15  tla001  阅读(331)  评论(0编辑  收藏  举报
个人网站 www.tla001.cn