系統編程--進程間通訊

  這篇進程間通訊,包含的內容比較多,包括最基本的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;
}
相關文章
相關標籤/搜索