linux系統編程--進程間通信


IPC方法

Linux環境下,進程地址空間相互獨立,每個進程各自有不同的用戶地址空間。任何一個進程的全局變量在另一個進程中都看不到,所以進程和進程之間不能相互訪問,

要交換數據必須通過內核在內核中開辟一塊緩沖區,進程1把數據從用戶空間拷到內核緩沖區,進程2再從內核緩沖區把數據讀走,內核提供的這種機制稱為進程間通信(IPC,InterProcess Communication)

                                                                                                 

         在進程間完成數據傳遞需要借助操作系統提供特殊的方法,如:文件(文件打開之后產生一個文件結構體在內核中,不同進程打開同一個文件,文件描述符是不相關的,但是在內核中映射到同一個緩存區)、

   管道、信號、共享內存、消息隊列、套接字、命名管道等。隨着計算機的蓬勃發展,

一些方法由於自身設計缺陷被淘汰或者棄用。現今常用的進程間通信方式有:

         ① 管道 (使用最簡單)

         ② 信號 (開銷最小)

         ③ 共享映射區 (無血緣關系)

         ④ 本地套接字 (最穩定)

 

管道

管道的概念:

管道是一種最基本的IPC機制,作用於有血緣關系的進程之間,完成數據傳遞。調用pipe系統函數即可創建一個管道。有如下特質:

         1. 其本質是一個偽文件(實為內核緩沖區)

    2. 由兩個文件描述符引用,一個表示讀端,一個表示寫端。

         3. 規定數據從管道的寫端流入管道,從讀端流出。

管道的原理: 管道實為內核使用環形隊列機制,借助內核緩沖區(4k)實現。

管道的局限性:

數據自己讀不能自己寫。

數據一旦被讀走,便不在管道中存在,不可反復讀取。

③ 由於管道采用半雙工通信方式。因此,數據只能在一個方向上流動。

只能在有公共祖先的進程間使用管道。

常見的通信方式有,單工通信、半雙工通信、全雙工通信。

pipe函數

創建管道

    int pipe(int pipefd[2]);               成功:0;失敗:-1,設置errno

函數調用成功返回r/w兩個文件描述符無需open,但需手動close規定:fd[0] → r; fd[1] → w,就像0對應標准輸入,1對應標准輸出一樣。

向管道文件讀寫數據其實是在讀寫內核緩沖區

管道創建成功以后,創建該管道的進程(父進程)同時掌握着管道的讀端和寫端。如何實現父子進程間通信呢?通常可以采用如下步驟:

                                                         

1. 父進程調用pipe函數創建管道,得到兩個文件描述符fd[0]、fd[1]指向管道的讀端和寫端。

2. 父進程調用fork創建子進程,那么子進程也有兩個文件描述符指向同一管道。

3. 父進程關閉管道讀端,子進程關閉管道寫端。父進程可以向管道中寫入數據,子進程將管道中的數據讀出。

    由於管道是利用環形隊列實現的,數據從寫端流入管道,從讀端流出,這樣就實現了進程間通信。

    練習:父子進程使用管道通信,父寫入字符串,子進程讀出並,打印到屏幕。【pipe.c】

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <errno.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>

void SYS_ERR(const char* str)
{
    perror(str);
    exit(1);
}

int main(int argc, char **argv)
{

    int fd[2];
    pid_t pid;
    char *str = "hello world\n";
    char *buf[128] = {0};
        int ret;
        ret = pipe(fd);
    if (ret == -1) {
        SYS_ERR("pipe error");
    }

    pid = fork();
    if (pid == -1) {
        SYS_ERR("fork error");
    }
    else if (pid > 0) {
        close(fd[0]);// 父進程關閉讀端
        write(fd[1], str, strlen(str));
        wait(NULL);// 回收子進程
        close(fd[1]);
    }
    else if (pid == 0) {
        close(fd[1]);// 子進程關閉寫端
         ret = read(fd[0], buf, sizeof(buf));
        write(STDOUT_FILENO, buf, ret);
        close(fd[0]);
    }

    return 0;
}

 

    思考:為甚么,程序中沒有使用sleep函數,但依然能保證子進程運行時一定會讀到數據呢?

管道的讀寫行為                                                          

 使用管道需要注意以下4種特殊情況(假設都是阻塞I/O操作,沒有設置O_NONBLOCK標志):

1. 如果所有指向管道寫端的文件描述符都關閉了(管道寫端引用計數為0),而仍然有進程從管道的讀端讀數據,那么管道中剩余的數據都被讀取后,再次read會返回0,就像讀到文件末尾一樣。

2. 如果有指向管道寫端的文件描述符沒關閉(管道寫端引用計數大於0),而持有管道寫端的進程也沒有向管道中寫數據,這時有進程從管道讀端讀數據,那么管道中剩余的數據都被讀取后,再次read會阻塞,直到管道中有數據可讀了才讀取數據並返回。

3. 如果所有指向管道讀端的文件描述符都關閉了(管道讀端引用計數為0),這時有進程向管道的寫端write,那么該進程會收到信號SIGPIPE,通常會導致進程異常終止。當然也可以對SIGPIPE信號實施捕捉,不終止進程。具體方法信號章節詳細介紹。

4. 如果有指向管道讀端的文件描述符沒關閉(管道讀端引用計數大於0),而持有管道讀端的進程也沒有從管道中讀數據,這時有進程向管道寫端寫數據,那么在管道被寫滿時再次write會阻塞,直到管道中有空位置了才寫入數據並返回。

總結:

① 讀管道:  1. 管道中有數據,read返回實際讀到的字節數

                     2. 管道中無數據:

        (1) 管道寫端被全部關閉,read返回0 (好像讀到文件結尾)

                             (2) 寫端沒有全部被關閉read阻塞等待(不久的將來可能有數據遞達,此時會讓出cpu)

② 寫管道:  1. 管道讀端全部被關閉進程異常終止(也可使用捕捉SIGPIPE信號,使進程不終止)

                      2. 管道讀端沒有全部關閉

        (1) 管道已滿,write阻塞。

                             (2) 管道未滿,write將數據寫入,並返回實際寫入的字節數

     練習:使用管道實現父子進程間通信,完成:ls | wc –l。假定父進程實現ls,子進程實現wc。

  ls命令正常會將結果集寫出到stdout,但現在會寫入管道的寫端;wc –l 正常應該從stdin讀取數據,但此時會從管道的讀端讀。                                                                                                                                                                                                                         

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void)
{
    pid_t pid;
    int fd[2];

    pipe(fd);
    pid = fork();

    if (pid == 0) {  //child
        close(fd[1]);                    //子進程從管道中讀數據,關閉寫端
        dup2(fd[0], STDIN_FILENO);        //讓wc從管道中讀取數據
        execlp("wc", "wc", "-l", NULL);    //wc命令默認從標准讀入取數據

    } else {

        close(fd[0]);    //父進程向管道中寫數據,關閉讀端
        dup2(fd[1], STDOUT_FILENO);        //將ls的結果寫入管道中
        execlp("ls", "ls", NULL);        //ls輸出結果默認對應屏幕
    }

    return 0;
}





/*
 *  程序不時的會出現先打印$提示符,再出程序運行結果的現象。
 *  這是因為:父進程執行ls命令,將輸出結果給通過管道傳遞給
 *  子進程去執行wc命令,這時父進程若先於子進程打印wc運行結果
 *  之前被shell使用wait函數成功回收,shell就會先於子進程打印
 *  wc運行結果之前打印$提示符。
 *  解決方法:讓子進程執行ls,父進程執行wc命令。或者在兄弟進程間完 *成。 */

 

程序執行,發現程序執行結束,shell還在阻塞等待用戶輸入。這是因為,shell → fork → ./pipe1, 程序pipe1的子進程將stdin重定向給管道,

父進程執行的ls會將結果集通過管道寫給子進程。若父進程在子進程打印wc的結果到屏幕之前被shell調用wait回收,shell就會先輸出$提示符。

     練習:使用管道實現兄弟進程間通信。 兄:ls  弟: wc -l  父:等待回收子進程。

     要求,使用“循環創建N個子進程”模型創建兄弟進程,使用循環因子i標示。注意管道讀寫行為。【pipe2.c】

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void)
{
    pid_t pid;
    int fd[2], i;
    
    pipe(fd);

    for (i = 0; i < 2; i++) {
        if((pid = fork()) == 0) {
            break;
        }
    }

    if (i == 0) {            //
        close(fd[0]);                //寫,關閉讀端
        dup2(fd[1], STDOUT_FILENO);        
        execlp("ls", "ls", NULL);    
    } else if (i == 1) {    //
        close(fd[1]);                //讀,關閉寫端
        dup2(fd[0], STDIN_FILENO);        
        execlp("wc", "wc", "-l", NULL);        
    } else {
        close(fd[0]);
        close(fd[1]);
        for(i = 0; i < 2; i++)        //兩個兒子wait兩次
            wait(NULL);
    }

    return 0;
}

 

     測試:是否允許,一個pipe有一個寫端,多個讀端呢?是否允許有一個讀端多個寫端呢?             【pipe3.c】

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

int main(void)
{
    pid_t pid;
    int fd[2], i, n;
    char buf[1024];

    int ret = pipe(fd);
    if(ret == -1){
        perror("pipe error");
        exit(1);
    }

    for(i = 0; i < 2; i++){
        if((pid = fork()) == 0)
            break;
        else if(pid == -1){
            perror("pipe error");
            exit(1);
        }
    }

    if (i == 0) {            
        close(fd[0]);                
        write(fd[1], "1.hello\n", strlen("1.hello\n"));
    } else if(i == 1) {    
        close(fd[0]);                
        write(fd[1], "2.world\n", strlen("2.world\n"));
    } else {
        close(fd[1]);       //父進程關閉寫端,留讀端讀取數據    
//        sleep(1);
        n = read(fd[0], buf, 1024);     //從管道中讀數據
        write(STDOUT_FILENO, buf, n);

        for(i = 0; i < 2; i++)        //兩個兒子wait兩次
            wait(NULL);
    }

    return 0;
}

 

    統計當前系統中進程ID大於10000的進程個數。

管道緩沖區大小

         可以使用ulimit –a 命令來查看當前系統中創建管道文件所對應的內核緩沖區大小 (4096bytes)。通常為:

                  pipe size            (512 bytes, -p) 8

         也可以使用fpathconf函數,借助參數        選項來查看。使用該宏應引入頭文件<unistd.h>

                   long fpathconf(int fd, int name);     成功:返回管道的大小         失敗:-1,設置errno

管道的優劣

         優點:簡單,相比信號,套接字實現進程間通信,簡單很多。

         缺點:1. 只能單向通信,雙向通信需建立兩個管道。

                    2. 只能用於父子、兄弟進程(有共同祖先)間通信。該問題后來使用fifo有名管道解決。

FIFO

FIFO常被稱為命名管道,以區分管道(pipe)。管道(pipe)只能用於“有血緣關系”的進程間。但通過FIFO,不相關的進程也能交換數據

FIFO是Linux基礎文件類型中的一種。但,FIFO文件在磁盤上沒有數據塊僅僅用來標識內核中一條通道。各進程可以打開這個文件進行read/write,實際上是在讀寫內核通道,這樣就實現了進程間通信。

創建方式:

1. 命令:mkfifo 管道名

2. 庫函數int mkfifo(const char *pathname,  mode_t mode);  成功:0; 失敗:-1

     一旦使用mkfifo創建了一個FIFO,就可以使用open打開它,常見的文件I/O函數都可用於fifo。如:close、read、write、unlink等。

 

管道無數據:關閉寫端,不會返回0,讀端阻塞

      若關閉讀端,寫端也會關閉

    【fifo_w.c】

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

void sys_err(char *str)
{
    perror(str);
    exit(-1);
}

int main(int argc, char *argv[])
{
    int fd, i;
    char buf[4096];

    if (argc < 2) {
        printf("Enter like this: ./a.out fifoname\n");
        return -1;
    }
    /*
    int ret = access("myfifo", F_OK);
    if (ret != 0) {
        mkfifo("myfifo", 0664);
    }
    */

    fd = open(argv[1], O_WRONLY);
    if (fd < 0) 
        sys_err("open");

    i = 0;
    while (1) {
        sprintf(buf, "hello itcast %d\n", i++);
        write(fd, buf, strlen(buf));
        sleep(2);
    }
    close(fd);

    return 0;
}

 

    【fifo_r.c】

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

void sys_err(char *str)
{
    perror(str);
    exit(1);
}
int main(int argc, char *argv[])
{
    int fd, len;
    char buf[4096];

    if (argc < 2) {
        printf("./a.out fifoname\n");
        return -1;
    }
    fd = open(argv[1], O_RDONLY);
    if (fd < 0) 
        sys_err("open");
    while (1) {
        len =read(fd, buf, sizeof(buf));
        write(STDOUT_FILENO, buf, len);
        sleep(1);           //多個讀端時應增加睡眠秒數,放大效果.
    }
    close(fd);

    return 0;
}

 

共享存儲映射

文件進程間通信

使用文件也可以完成IPC,理論依據是,fork后,父子進程共享文件描述符。也就共享打開的文件。

 練習:編程測試,父子進程共享打開的文件。借助文件進行進程間通信。【fork_shared_fd.c】

/* 
 *父子進程共享打開的文件描述符------使用文件完成進程間通信.
 */
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/wait.h>


int main(void)
{
    int fd1, fd2; pid_t pid;
    char buf[1024];
    char *str = "---------test for shared fd in parent child process-----\n";

    pid = fork();
    if (pid < 0) {
        perror("fork error");
        exit(1);
    } else if (pid == 0) {
        fd1 = open("test.txt", O_RDWR);
        if (fd1 < 0) {
            perror("open error");
            exit(1);
        }
        write(fd1, str, strlen(str));
        printf("child wrote over...\n");
    } else {
        fd2 = open("test.txt", O_RDWR);
        if (fd2 < 0) {
            perror("open error");
            exit(1);
        }
        sleep(1);                   //保證子進程寫入數據
        int len = read(fd2, buf, sizeof(buf));
        write(STDOUT_FILENO, buf, len);

        wait(NULL);
    }

    return 0;
}
通過文件通信

 

思考,無血緣關系的進程可以打開同一個文件進行通信嗎?為什么?

/*
 * 后執行,嘗試讀取另外一個進程寫入文件的內容
 */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

int main(void)
{
    char buf[1024];
    char *str = "----------test2 write secesuss--------\n";
    int ret;

    sleep(2);   //睡眠2秒,保證test1將數據寫入test.txt文件

    int fd = open("test.txt", O_RDWR);

    //嘗試讀取test.txt文件中test1寫入的數據
    ret = read(fd, buf, sizeof(buf));   

    //將讀到的數據打印至屏幕
    write(STDOUT_FILENO, buf, ret);

    //寫入數據到文件test.txth中, 未修改讀寫位置
    write(fd, str, strlen(str));

    printf("test2 read/write finish\n");

    close(fd);

    return 0;
}
/*
 * 先執行,將數據寫入文件test.txt
 */
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

#define N 5

int main(void)
{
    char buf[1024];
    char *str = "--------------secesuss-------------\n";
    int ret;

    int fd = open("test.txt", O_RDWR|O_TRUNC|O_CREAT, 0664);

    //直接打開文件寫入數據
    write(fd, str, strlen(str));
    printf("test1 write into test.txt finish\n");

    sleep(N);

    lseek(fd, 0, SEEK_SET);
    ret = read(fd, buf, sizeof(buf));
    ret = write(STDOUT_FILENO, buf, ret);

    if (ret == -1) {
        perror("write second error");
        exit(1);
    }

    close(fd);

    return 0;
}

 

存儲映射I/O

         存儲映射I/O (Memory-mapped I/O) 使一個磁盤文件與存儲空間中的一個緩沖區相映射。於是當從緩沖區中取數據,就相當於讀文件中的相應字節。於此類似,將數據存入緩沖區,則相應的字節就自動寫入文件。這樣,就可在不適用read和write函數的情況下,使用地址(指針)完成I/O操作。

簡單的說就是將磁盤映射到內存,然后通過操作內存的方法來改變磁盤數據

         使用這種方法,首先應通知內核將一個指定文件映射到存儲區域中。這個映射工作可以通過mmap函數來實現

                                                                                                  

mmap函數

void *mmap(void *adrr, size_t length, int prot, int flags, int fd, off_t offset);

返回:成功:返回創建的映射區首地址失敗:MAP_FAILED

參數:    

         addr:       建立映射區的首地址,由Linux內核指定。使用時,直接傳遞NULL

         length: 欲創建映射區的大小

         prot:      映射區權限 PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE ,還有執行

         flags:     標志位參數(常用於設定更新物理區域、設置共享、創建匿名映射區)

                          MAP_SHARED:  會將映射區所做的操作反映到物理設備(磁盤)上。

                          MAP_PRIVATE: 映射區所做的修改不會反映到物理設備。

         fd:         用來建立映射區的文件描述符

         offset: 映射文件的偏移(4k的整數倍)

munmap函數

         同malloc函數申請內存空間類似的,mmap建立的映射區在使用結束后也應調用類似free的函數來釋放。

    int munmap(void *addr, size_t length);  成功:0; 失敗:-1

         借鑒malloc和free函數原型,嘗試裝自定義函數smalloc,sfree來完成映射區的建立和釋放。思考函數接口該如何設計?【smalloc.c】

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

void *smalloc(size_t size)
{
    void *p;

    p = mmap(NULL, size, PROT_READ|PROT_WRITE, 
            MAP_SHARED|MAP_ANON, -1, 0);
    if (p == MAP_FAILED) {        
        p = NULL;
    }

    return p;
}

void sfree(void *ptr, size_t size)
{
    munmap(ptr, size);
}

int main(void)
{
    int *p;
    pid_t pid;
    
    p = smalloc(4);

    pid = fork();                //創建子進程
    if (pid == 0) {
        *p = 2000;
        printf("child, *p = %d\n", *p);
    } else {
        sleep(1);
        printf("parent, *p = %d\n", *p);
    }

    sfree(p, 4);

    return 0;
}

 

mmap注意事項

【mmap.c】

#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>

void sys_err(char *str)
{
    perror(str);
    exit(1);
}

int main(void)
{
    char *mem;
    int len = 0;

    int fd = open("hello678", O_RDWR|O_CREAT|O_TRUNC, 0644);
    if (fd < 0)
        sys_err("open error");
    len = lseek(fd, 3, SEEK_SET);   //獲取文件大小,根據文件大小創建映射區
    write(fd, "\0", 1);              //實質性完成文件拓展    或者使用ftruncate 函數,不需要IO操作就可以拓展文件大小
    len = lseek(fd, 0, SEEK_END);
    
    printf("The length of file = %d\n", len);

    mem = mmap(NULL, len, PROT_WRITE, MAP_PRIVATE, fd, 0);
    if (mem == MAP_FAILED)            //出錯判斷
        sys_err("mmap err: ");
    close(fd);

    strcpy(mem, "aaa");
    printf("%s\n", mem);

    if (munmap(mem,  len) < 0)
        sys_err("munmap");

    return 0;
}

 

思考:

1. 如果mem++,munmap可否成功? // 不能,會出錯

2. 如果open時O_RDONLY, mmap時PROT參數指定PROT_READ|PROT_WRITE會怎樣? // 出錯

3. 如果文件偏移量為1000會怎樣?// 無效的參數,必須是4k的整數倍

5. mmap什么情況下會調用失敗?//

6. 對mem越界操作會怎樣?// 不會報錯,但是不安全,數據沒保障

7. 文件描述符先關閉,對mmap映射有沒有影響?// 沒有影響

4. 如果不檢測mmap的返回值,會怎樣?

8. 可以open的時候O_CREAT一個新文件來創建映射區嗎?

總結:使用mmap時務必注意以下事項:

1.       創建映射區的過程中,隱含着一次對映射文件的讀操作。(所以創建文件的時候要注意權限的問題)

2.       當MAP_SHARED時,要求:映射區的權限應 <=文件打開的權限(出於對映射區的保護)。而MAP_PRIVATE則無所謂,因為mmap中的權限是對內存的限制。

3.       映射區的釋放與文件關閉無關。只要映射建立成功,文件可以立即關閉

4.       特別注意當映射文件大小為0時,不能創建映射區。所以:用於映射的文件必須要有實際大小!! mmap使用時常常會出現總線錯誤,通常是由於共享文件存儲空間大小引起的。

5.       munmap傳入的地址一定是mmap的返回地址。堅決杜絕指針++操作

6.       如果文件偏移量必須為4K的整數倍

7.       mmap創建映射區出錯概率非常高,一定要檢查返回值,確保映射區建立成功再進行后續操作。

mmap父子進程通信

         父子等有血緣關系的進程之間也可以通過mmap建立的映射區來完成數據通信。但相應的要在創建映射區的時候指定對應的標志位參數flags:

    MAP_PRIVATE:  (私有映射)  父子進程各自獨占映射區;

         MAP_SHARED:  (共享映射)  父子進程共享映射區;

        練習:父進程創建映射區,然后fork子進程,子進程修改映射區內容,而后,父進程讀取映射區內容,查驗是否共享。 【fork_mmap.c】

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>

int var = 100;

int main(void)
{
    int *p;
    pid_t pid;

    int fd;
    fd = open("temp", O_RDWR|O_CREAT|O_TRUNC, 0644);
    if(fd < 0){
        perror("open error");
        exit(1);
    }
    unlink("temp");                //刪除臨時文件目錄項,使之具備被釋放條件.
    ftruncate(fd, 4);

    p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    //p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
    if(p == MAP_FAILED){        //注意:不是p == NULL
        perror("mmap error");
        exit(1);
    }
    close(fd);                    //映射區建立完畢,即可關閉文件

    pid = fork();                //創建子進程
    if(pid == 0){
        *p = 2000;
        var = 1000;
        printf("child, *p = %d, var = %d\n", *p, var);
    } else {
        sleep(1);
        printf("parent, *p = %d, var = %d\n", *p, var);
        wait(NULL);

        int ret = munmap(p, 4);                //釋放映射區
        if (ret == -1) {
            perror("munmap error");
            exit(1);
        }
    }

    return 0;
}

 

結論:父子進程共享:1. 打開的文件  2. mmap建立的映射區(但必須要使用MAP_SHARED)

匿名映射

         通過使用我們發現,使用映射區來完成文件讀寫操作十分方便,父子進程間通信也較容易。但缺陷是,每次創建映射區一定要依賴一個文件才能實現。

通常為了建立映射區要open一個temp文件,創建好了再unlink、close掉,比較麻煩。 可以直接使用匿名映射來代替。其實Linux系統給我們提供了創建匿名映射區的方法,無需依賴一個文件即可創建映射區。

同樣需要借助標志位參數flags來指定。使用MAP_ANONYMOUS (或MAP_ANON), 如:

         int *p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);

    "4"隨意舉例,該位置表大小,可依實際需要填寫。【fork_map_anon_linux.c】

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

int main(void)
{
    int *p;
    pid_t pid;
    
    int fd = open("/dev/zero", O_RDWR);
    //p = mmap(NULL, 400, PROT_READ|PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
    p = mmap(NULL, 400, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if(p == MAP_FAILED){        //注意:不是p == NULL
        perror("mmap error");
        exit(1);
    }
    close(fd);

    pid = fork();                //創建子進程
    if(pid == 0){
        *p = 2000;
        printf("child, *p = %d\n", *p);
    } else {
        sleep(1);
        printf("parent, *p = %d\n", *p);
    }

    munmap(p, 400);                //釋放映射區

    return 0;
}

 

需注意的是,MAP_ANONYMOUS和MAP_ANON這兩個宏是Linux操作系統特有的宏。在類Unix系統中如無該宏定義,可使用如下兩步來完成匿名映射區的建立。

         ① fd = open("/dev/zero", O_RDWR);

         ② p = mmap(NULL, size, PROT_READ|PROT_WRITE, MMAP_SHARED, fd, 0);

mmap無血緣關系進程間通信

實質上mmap是內核借助文件幫我們創建了一個映射區,多個進程之間利用該映射區完成數據傳遞。由於內核空間多進程共享,因此無血緣關系的進程間也可以使用mmap來完成通信。

只要設置相應的標志位參數flags即可。若想實現共享,當然應該使用MAP_SHARED了。【mmp_w.c/mmp_r.c】

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

struct STU {
    int id;
    char name[20];
    char sex;
};

void sys_err(char *str)
{
    perror(str);
    exit(1);
}

int main(int argc, char *argv[])
{
    int fd;
    struct STU student = {10, "xiaoming", 'm'};
    char *mm;

    if (argc < 2) {
        printf("./a.out file_shared\n");
        exit(-1);
    }

    fd = open(argv[1], O_RDWR | O_CREAT, 0664);
    ftruncate(fd, sizeof(student));

    mm = mmap(NULL, sizeof(student), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (mm == MAP_FAILED)
        sys_err("mmap");

    close(fd);

    while (1) {
        memcpy(mm, &student, sizeof(student));
        student.id++;
        sleep(1);
    }

    munmap(mm, sizeof(student));

    return 0;
}
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <string.h>

struct STU {
    int id;
    char name[20];
    char sex;
};

void sys_err(char *str)
{
    perror(str);
    exit(-1);
}

int main(int argc, char *argv[])
{
    int fd;
    struct STU student;
    struct STU *mm;

    if (argc < 2) {
        printf("./a.out file_shared\n");
        exit(-1);
    }

    fd = open(argv[1], O_RDONLY);
    if (fd == -1)
        sys_err("open error");

    mm = mmap(NULL, sizeof(student), PROT_READ, MAP_SHARED, fd, 0);
    if (mm == MAP_FAILED)
        sys_err("mmap error");
    
    close(fd);

    while (1) {
        printf("id=%d\tname=%s\t%c\n", mm->id, mm->name, mm->sex);
        usleep(10000);
    }

    munmap(mm, sizeof(student));

    return 0;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM