天天看點

linux-程序間通信1 學習目标2 程序間通信相關概念3 管道-pipe4 FIFO5 記憶體映射區

linux-程序間通信

  • 1 學習目标
  • 2 程序間通信相關概念
    • 2.1 什麼是程序間通信
    • 2.2 程序間通信的方式
  • 3 管道-pipe
    • 3.1管道的概念
    • 3.2管道的原理
    • 3.3管道的局限性
    • 3.4建立管道-pipe函數
    • 3.5父子程序使用管道通信
    • 3.6 管道練習
    • 3.7 管道的讀寫行為
    • 3.8 如何設定管道為非阻塞
    • 3.9 如何檢視管道緩沖區大小
  • 4 FIFO
    • 4.1 FIFO介紹
    • 4.2 建立管道
    • 4.3 使用FIFO完成兩個程序通信
  • 5 記憶體映射區
    • 5.1 存儲映射區介紹
    • 5.2 mmap函數
    • 5.3 munmap函數
    • 5.4 mmap注意事項
    • 5.5 有關mmap函數的使用總結
    • 5.6 mmap函數相關思考題
    • 5.7 mmap應用練習

1 學習目标

  • 熟練使用pipe進行父子程序間通信
  • 熟練使用pipe進行兄弟程序間通信
  • 熟練使用fifo進行無血緣關系的程序間通信
  • 使用mmap進行有血緣關系的程序間通信
  • 使用mmap進行無血緣關系的程序間通信

2 程序間通信相關概念

2.1 什麼是程序間通信

Linux環境下,程序位址空間互相獨立,每個程序各自有不同的使用者位址空間。任何一個程序的全局變量在另一個程序中都看不到,是以程序和程序之間不能互相通路,要交換資料必須通過核心,在核心中開辟一塊緩沖區,程序1把資料從使用者空間拷到核心緩沖區,程序2再從核心緩沖區把資料讀走,核心提供的這種機制稱為程序間通信(IPC,InterProcess Communication)。

linux-程式間通信1 學習目标2 程式間通信相關概念3 管道-pipe4 FIFO5 記憶體映射區

2.2 程序間通信的方式

在程序間完成資料傳遞需要借助作業系統提供特殊的方法,如:檔案、管道、信号、共享記憶體、消息隊列、套接字、命名管道等。随着計算機的蓬勃發展,一些方法由于自身設計缺陷被淘汰或者棄用。現今常用的程序間通信方式有:

  • 管道 (使用最簡單)
  • 信号 (開銷最小)
  • 共享映射區 (無血緣關系)
  • 本地套接字 (最穩定)

3 管道-pipe

3.1管道的概念

管道是一種最基本的IPC機制,也稱匿名管道,應用于有血緣關系的程序之間,完成資料傳遞。調用pipe函數即可建立一個管道。

linux-程式間通信1 學習目标2 程式間通信相關概念3 管道-pipe4 FIFO5 記憶體映射區

有如下特質:

  • 管道的本質是一塊核心緩沖區
  • 由兩個檔案描述符引用,一個表示讀端,一個表示寫端。
  • 規定資料從管道的寫端流入管道,從讀端流出。
  • 當兩個程序都終結的時候,管道也自動消失。
  • 管道的讀端和寫端預設都是阻塞的。

3.2管道的原理

  • 管道的實質是核心緩沖區,内部使用環形隊列實作。
  • 預設緩沖區大小為4K,可以使用ulimit -a指令擷取大小。
  • 實際操作過程中緩沖區會根據資料壓力做适當調整。

3.3管道的局限性

  • 資料一旦被讀走,便不在管道中存在,不可反複讀取。
  • 資料隻能在一個方向上流動,若要實作雙向流動,必須使用兩個管道
  • 隻能在有血緣關系的程序間使用管道。

3.4建立管道-pipe函數

  • 函數作用:

    建立一個管道

  • 函數原型:

    int pipe(int fd[2]);

  • 函數參數:

    若函數調用成功,fd[0]存放管道的讀端,fd[1]存放管道的寫端

  • 傳回值:

    成功傳回0;

    失敗傳回-1,并設定errno值。

函數調用成功傳回讀端和寫端的檔案描述符,其中fd[0]是讀端,fd[1]是寫端,向管道讀寫資料是通過使用這兩個檔案描述符進行的,讀寫管道的實質是操作核心緩沖區。

管道建立成功以後,建立該管道的程序(父程序)同時掌握着管道的讀端和寫端。如何實作父子程序間通信呢?

3.5父子程序使用管道通信

一個程序在由pipe()建立管道後,一般再fork一個子程序,然後通過管道實作父子程序間的通信(是以也不難推出,隻要兩個程序中存在血緣關系,這裡的血緣關系指的是具有共同的祖先,都可以采用管道方式來進行通信)。父子程序間具有相同的檔案描述符,且指向同一個管道pipe,其他沒有關系的程序不能獲得pipe()産生的兩個檔案描述符,也就不能利用同一個管道進行通信。

第一步:父程序建立管道

linux-程式間通信1 學習目标2 程式間通信相關概念3 管道-pipe4 FIFO5 記憶體映射區

第二步:父程序fork出子程序

linux-程式間通信1 學習目标2 程式間通信相關概念3 管道-pipe4 FIFO5 記憶體映射區

第三步:父程序關閉fd[0],子程序關閉fd[1]

linux-程式間通信1 學習目标2 程式間通信相關概念3 管道-pipe4 FIFO5 記憶體映射區

建立步驟總結:

  • 父程序調用pipe函數建立管道,得到兩個檔案描述符fd[0]和fd[1],分别指向管道的讀端和寫端。
  • 父程序調用fork建立子程序,那麼子程序也有兩個檔案描述符指向同一管。
  • 父程序關閉管道讀端,子程序關閉管道寫端。父程序可以向管道中寫入資料,子程序将管道中的資料讀出,這樣就實作了父子程序間通信。

3.6 管道練習

  • 一個程序能否使用管道完成讀寫操作呢?
  • 使用管道完成父子程序間通信?
  • 父子程序間通信, 實作ps aux | grep bash

    使用execlp函數和dup2函數

    linux-程式間通信1 學習目标2 程式間通信相關概念3 管道-pipe4 FIFO5 記憶體映射區
/*
 * @Descripttion: 使用pipe完成ps aux | grep bash
 * @version: 
 * @Author: Lzy
 * @Date: 2021-07-21 16:50:31
 * @LastEditors: Lzy
 * @LastEditTime: 2021-07-21 17:52:46
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>

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

    int fd[2];
    int ret = pipe(fd);
    if (ret < 0)
    {
        perror("pipe error");
        return -1;
    }
    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fork error");
    }
    else if (pid > 0)
    {
        close(fd[0]);
        dup2(fd[1], STDOUT_FILENO);
        execlp("ps", "ps", "aux", NULL);
        perror("excelp error");
        wait(NULL);
    }
    else
    {
        close(fd[1]);
        dup2(fd[0], STDIN_FILENO);
        execlp("grep", "grep", "bash", NULL);
        perror("execlp error");
    }
    return 0;
} 
           
  • 兄弟程序間通信, 實作ps aux | grep bash

    使用execlp函數和dup2函數

    父程序要調用waitpid函數完成對子程序的回收

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

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

	//建立一個管道
	ret = pipe(fd);
	if(ret<0)
	{
		perror("pipe error");
		return -1;
	}

	int i = 0;
	int n = 2;
	for(i=0; i<n; i++)
	{
		//建立子程序
		pid = fork();
		if(pid<0)
		{
			perror("fork error");
			return -1;
		}
		else if(pid==0)
		{
			break;
		}

	}

	if(i==n)
	{
		close(fd[0]);	
		close(fd[1]);	

		pid_t wpid;
		int status;

		while(1)
		{
			//等待回收子程序
			wpid = waitpid(-1, &status, WNOHANG);	
			if(wpid==0) //沒有子程序退出
			{
				sleep(1);
				continue;
			}
			else if(wpid==-1) //已經沒有子程序
			{
				printf("no child is living, wpid==[%d]\n", wpid);
				exit(0);
			}
			else if(wpid>0)
			{
				if(WIFEXITED(status)) //正常退出
				{
					printf("child normal exited, status==[%d]\n", WEXITSTATUS(status));
				}
				else if(WIFSIGNALED(status)) //被信号殺死
				{
					 printf("child killed by signo==[%d]\n", WTERMSIG(status));
				}
			}
			
		}
	}
	
	//第一個子程序
	if(i==0)
	{
		
		close(fd[0]);

		//将标準輸出重定向到管道到寫端
        dup2(fd[1], STDOUT_FILENO);
		execlp("ps", "ps", "aux", NULL);
		perror("execlp error");

		close(fd[1]);
	}

	//第二個子程序
	if(i==1)
	{
		printf("child: fpid==[%d], cpid==[%d]\n", getppid(), getpid());
		close(fd[1]);

		//将标準輸入重定向到管道到讀端
		dup2(fd[0], STDIN_FILENO);
		execlp("grep", "grep", "--color", "bash", NULL);
		perror("execlp error");

		close(fd[0]);
	}

	return 0;
}

           

3.7 管道的讀寫行為

  • 讀操作
    • 有資料:read正常讀,傳回讀出的位元組數
    • 無資料:寫端全部關閉:read解除阻塞,傳回0, 相當于讀檔案讀到了尾部;沒有全部關閉:read阻塞。
  • 寫操作
    • 讀端全部關閉:管道破裂,程序終止, 核心給目前程序發SIGPIPE信号
    • 讀端沒全部關閉:緩沖區寫滿了,write阻塞;緩沖區沒有滿,繼續write。

3.8 如何設定管道為非阻塞

預設情況下,管道的讀寫兩端都是阻塞的,若要設定讀或者寫端為非阻塞,則可參考下列三個步驟進行:

第1步:

int flags = fcntl(fd[0], F_GETFL, 0);

第2步:

flag |= O_NONBLOCK;

第3步:

fcntl(fd[0], F_SETFL, flags);

若是讀端設定為非阻塞:

  • 寫端沒有關閉,管道中沒有資料可讀,則read傳回-1;
  • 寫端沒有關閉,管道中有資料可讀,則read傳回實際讀到的位元組數
  • 寫端已經關閉,管道中有資料可讀,則read傳回實際讀到的位元組數
  • 寫端已經關閉,管道中沒有資料可讀,則read傳回0

3.9 如何檢視管道緩沖區大小

  • 指令

    ulimit -a

  • 函數
long fpathconf(int fd, int name);
printf("pipe size==[%ld]\n", fpathconf(fd[0], _PC_PIPE_BUF));
printf("pipe size==[%ld]\n", fpathconf(fd[1], _PC_PIPE_BUF));
           

4 FIFO

4.1 FIFO介紹

FIFO常被稱為命名管道,以區分管道(pipe)。管道(pipe)隻能用于“有血緣關系”的程序間通信。但通過FIFO,不相關的程序也能交換資料。

FIFO是Linux基礎檔案類型中的一種(檔案類型為p,可通過ls -l檢視檔案類型)。但FIFO檔案在磁盤上沒有資料塊,檔案大小為0,僅僅用來辨別核心中一條通道。程序可以打開這個檔案進行read/write,實際上是在讀寫核心緩沖區,這樣就實作了程序間通信。

4.2 建立管道

  • 方式1-使用指令 mkfifo

    指令格式: mkfifo 管道名

    例如:mkfifo myfifo

  • 方式2-使用函數

    int mkfifo(const char *pathname, mode_t mode);

    參數說明和傳回值可以檢視man 3 mkfifo

當建立了一個FIFO,就可以使用open函數打開它,常見的檔案I/O函數都可用于FIFO。如:close、read、write、unlink等。

FIFO嚴格遵循先進先出(first in first out),對FIFO的讀總是從開始處傳回資料,對它們的寫則把資料添加到末尾。它們不支援諸如lseek()等檔案定位操作。

4.3 使用FIFO完成兩個程序通信

  • 使用FIFO完成兩個程序通信的示意圖
    linux-程式間通信1 學習目标2 程式間通信相關概念3 管道-pipe4 FIFO5 記憶體映射區

思路:

  • 程序A:
    • 建立一個fifo檔案:myfifo
    • 調用open函數打開myfifo檔案
    • 調用write函數寫入一個字元串如:“hello world”(其實是将資料寫入到了核心緩沖區)
    • 調用close函數關閉myfifo檔案
  • 程序B:
    • 調用open函數打開myfifo檔案
    • 調用read函數讀取檔案内容(其實就是從核心中讀取資料)
    • 列印顯示讀取的内容
    • 調用close函數關閉myfifo檔案

注意:myfifo檔案是在程序A中建立的,如果先啟動程序B會報錯。思考一下如何解決這個問題呢???

程序A寫入資料

/*
 * @Descripttion: 
 * @version: 
 * @Author: Lzy
 * @Date: 2021-07-22 16:54:53
 * @LastEditors: Lzy
 * @LastEditTime: 2021-07-22 17:33:56
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

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

    // int access(const char *pathname, int mode);
    int ret = access("./myfifo", F_OK);
    if (ret == -1)
    { 
        //檔案不存在
        // int mkfifo(const char *pathname, mode_t mode);
        ret = mkfifo("./myfifo", 0777);
        if (ret == -1)
        {
            perror("mkfifo error:");
            return -1;
        }
    }

    int fd = open("./myfifo",O_RDWR);
    if (fd == -1){
        perror("open error:");
        return -1;
    }

    write(fd,"hello world!\n",sizeof("hello world"));
    
    sleep(20);
    close(fd);

    return 0;
}
           

程序B讀取資料

/*
 * @Descripttion: 
 * @version: 
 * @Author: Lzy
 * @Date: 2021-07-22 16:54:53
 * @LastEditors: Lzy
 * @LastEditTime: 2021-07-22 18:01:43
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

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

    // int access(const char *pathname, int mode);
    int ret = access("./myfifo", F_OK);
    if (ret == -1)
    { 
        //檔案不存在
        // int mkfifo(const char *pathname, mode_t mode);
        ret = mkfifo("./myfifo", 0777);
        if (ret == -1)
        {
            perror("mkfifo error:");
            return -1;
        }
    }

    int fd = open("./myfifo",O_RDWR);
    if (fd == -1){
        perror("open error:");
        return -1;
    }

    char str[20];
    memset(str,0,sizeof(str));
    read(fd,str,sizeof(str));
    printf("read is %s",str);
    
    close(fd);
    return 0;
}
           

5 記憶體映射區

5.1 存儲映射區介紹

存儲映射I/O (Memory-mapped I/O) 使一個磁盤檔案與存儲空間中的一個緩沖區相映射。從緩沖區中取資料,就相當于讀檔案中的相應位元組;将資料寫入緩沖區,則會将資料寫入檔案。這樣,就可在不使用read和write函數的情況下,使用位址(指針)完成I/O操作。

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

linux-程式間通信1 學習目标2 程式間通信相關概念3 管道-pipe4 FIFO5 記憶體映射區

5.2 mmap函數

  • 函數作用:

    建立存儲映射區

  • 函數原型

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

  • 函數傳回值:

    成功:傳回建立的映射區首位址;

    失敗:MAP_FAILED宏

  • 參數:
    • addr: 指定映射的起始位址, 通常設為NULL, 由系統指定
    • length:映射到記憶體的檔案長度
    • prot: 映射區的保護方式, 最常用的:

      讀:PROT_READ

      寫:PROT_WRITE

      讀寫:PROT_READ | PROT_WRITE

    • flags: 映射區的特性, 可以是

      MAP_SHARED: 寫入映射區的資料會寫回檔案, 且允許其他映射該檔案的程序共享。

      MAP_PRIVATE: 對映射區的寫入操作會産生一個映射區的複制(copy-on-write), 對此區域所做的修改不會寫回原檔案。

    • fd:由open傳回的檔案描述符, 代表要映射的檔案。
    • offset:以檔案開始處的偏移量, 必須是4k的整數倍, 通常為0, 表示從檔案頭開始映射。

5.3 munmap函數

  • 函數作用:

    釋放由mmap函數建立的存儲映射區

  • 函數原型:

    int munmap(void *addr, size_t length);

  • 傳回值:

    成功:傳回0

    失敗:傳回-1,設定errno值

  • 函數參數:
    • addr:調用mmap函數成功傳回的映射區首位址
    • length:映射區大小(mmap函數的第二個參數)

5.4 mmap注意事項

  • 建立映射區的過程中,隐含着一次對映射檔案的讀操作,将檔案内容讀取到映射區
  • 當MAP_SHARED時,要求:映射區的權限應 <=檔案打開的權限(出于對映射區的保護)。而MAP_PRIVATE則無所謂,因為mmap中的權限是對記憶體的限制。
  • 映射區的釋放與檔案關閉無關,隻要映射建立成功,檔案可以立即關閉。
  • 特别注意,當映射檔案大小為0時,不能建立映射區。是以,用于映射的檔案必須要有實際大小;mmap使用時常常會出現總線錯誤,通常是由于共享檔案存儲空間大小引起的。
  • munmap傳入的位址一定是mmap的傳回位址。堅決杜絕指針++操作。
  • 檔案偏移量必須為0或者4K的整數倍
  • mmap建立映射區出錯機率非常高,一定要檢查傳回值,確定映射區建立成功再進行後續操作。

5.5 有關mmap函數的使用總結

  • 第一個參數寫成NULL
  • 第二個參數要映射的檔案大小 > 0
  • 第三個參數:PROT_READ 、PROT_WRITE
  • 第四個參數:MAP_SHARED 或者 MAP_PRIVATE
  • 第五個參數:打開的檔案對應的檔案描述符
  • 第六個參數:4k的整數倍

5.6 mmap函數相關思考題

  • 可以open的時候O_CREAT一個新檔案來建立映射區嗎?
  • 如果open時O_RDONLY, mmap時PROT參數指定PROT_READ|PROT_WRITE會怎樣?
  • mmap映射完成之後, 檔案描述符關閉,對mmap映射有沒有影響?
  • 如果檔案偏移量為1000會怎樣?
  • 對mem越界操作會怎樣?
  • 如果mem++,munmap可否成功?
  • mmap什麼情況下會調用失敗?
  • 如果不檢測mmap的傳回值,會怎樣?

5.7 mmap應用練習

  • 練習1:使用mmap完成對檔案的讀寫操作
/*
 * @Descripttion: 使用mmap完成對檔案的讀寫操作,傳入第一個參數為檔案名,若沒傳入則使用預設檔案"test.log"。
 * @version: 
 * @Author: Lzy
 * @Date: 2021-07-22 14:36:29
 * @LastEditors: Lzy
 * @LastEditTime: 2021-07-22 15:02:25
 */

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

int main(int argc, char **argv)
{
    int fd = 0;
    char *filename = NULL;
    if (argc > 1)
    {
        //傳入檔案名,使用提供檔案
        filename = argv[1];
    }
    else
    {
        //使用預設檔案名
        filename = "test.log";
    }

    printf("%s\n", filename);

    // int access(const char *pathname, int mode);
    fd = open(filename, O_RDWR | O_CREAT, 0777);
    if (fd == -1)
    {
        perror("open error:");
    }
    // ssize_t write(int fd, const void *buf, size_t count);
    //使檔案大小大于0,mmap函數要求
    write(fd, "1", strlen("1"));

    //擷取檔案大小
    struct stat statbuf;
    int ret = stat(filename, &statbuf);
    if (ret < 0)
    {
        perror("stat error:");
    }

    //    void *mmap(void *addr, size_t length, int prot, int flags,
    //               int fd, off_t offset);
    void *addr = mmap(NULL, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED)
    {
        perror("mmap error");
        return -1;
    }

    memcpy(addr, "hello world!", strlen("hello world!"));

    char *str = (char *)addr;
    printf("read is [%s]\n", str);

    return 0;
}
           
  • 練習:2:使用mmap完成父子程序間通信
    • 圖解說明
      linux-程式間通信1 學習目标2 程式間通信相關概念3 管道-pipe4 FIFO5 記憶體映射區
    • 思路
      • 調用mmap函數建立存儲映射區,傳回映射區首位址ptr
      • 調用fork函數建立子程序,子程序也擁有了映射區首位址
      • 父子程序可以通過映射區首位址指針ptr完成通信
      • 調用munmap函數釋放存儲映射區
/*
 * @Descripttion: 使用mmap完成父子程序間通信,傳入第一個參數為檔案名,若沒傳入則使用預設檔案"test.log"。
 * @version: 
 * @Author: Lzy
 * @Date: 2021-07-22 14:36:29
 * @LastEditors: Lzy
 * @LastEditTime: 2021-07-22 16:46:01
 */

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

int main(int argc, char **argv)
{
    int fd = 0;
    char *filename = NULL;
    if (argc > 1)
    {
        //傳入檔案名,使用提供檔案
        filename = argv[1];
    }
    else
    {
        //使用預設檔案名
        filename = "test.log";
    }

    printf("%s\n", filename);

    // int access(const char *pathname, int mode);
    fd = open(filename, O_RDWR | O_CREAT, 0777);
    if (fd == -1)
    {
        perror("open error:");
    }
    // ssize_t write(int fd, const void *buf, size_t count);
    //使檔案大小大于0,mmap函數要求
    write(fd, "1", strlen("1"));

    //擷取檔案大小
    struct stat statbuf;
    int ret = stat(filename, &statbuf);
    if (ret < 0)
    {
        perror("stat error:");
    }

    //    void *mmap(void *addr, size_t length, int prot, int flags,
    //               int fd, off_t offset);
    void *addr = mmap(NULL, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED)
    {
        perror("mmap error");
        return -1;
    }

    pid_t pid = fork();
    if(pid < 0){
        perror("fork error:");
    }else if(pid > 0){
        memcpy(addr,"hello world!",strlen("hello world!"));
        wait(NULL);
        munmap(addr,statbuf.st_size);
    }else if(pid == 0){
        char str [20];
        memset(str,0,sizeof(str));
        memcpy(str,addr,strlen(addr));
        printf("read is [%s]\n",str);
    }

    return 0;
}
           
  • 練習3:使用mmap完成沒有血緣關系的程序間通

    思路:兩個程序都打開相同的檔案,然後調用mmap函數建立存儲映射區,這樣兩個程序共享同一個存儲映射區。

程序一寫檔案:

/*
 * @Descripttion: 使用mmap完成沒有血緣關系的程序間通,傳入第一個參數為檔案名,若沒傳入則使用預設檔案"test.log"。
 * @version: 
 * @Author: Lzy
 * @Date: 2021-07-22 14:36:29
 * @LastEditors: Lzy
 * @LastEditTime: 2021-07-22 16:52:25
 */

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

int main(int argc, char **argv)
{
    int fd = 0;
    char *filename = NULL;
    if (argc > 1)
    {
        //傳入檔案名,使用提供檔案
        filename = argv[1];
    }
    else
    {
        //使用預設檔案名
        filename = "test.log";
    }

    printf("%s\n", filename);

    // int access(const char *pathname, int mode);
    fd = open(filename, O_RDWR | O_CREAT, 0777);
    if (fd == -1)
    {
        perror("open error:");
    }
    
    // ssize_t write(int fd, const void *buf, size_t count);
    //使檔案大小大于0,mmap函數要求
    write(fd, "1", strlen("1"));

    //擷取檔案大小
    struct stat statbuf;
    int ret = stat(filename, &statbuf);
    if (ret < 0)
    {
        perror("stat error:");
    }

    //    void *mmap(void *addr, size_t length, int prot, int flags,
    //               int fd, off_t offset);
    void *addr = mmap(NULL, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED)
    {
        perror("mmap error");
        return -1;
    }

    char str [20];
    memset(str,0,sizeof(str));
    memcpy(str,addr,strlen(addr));
    printf("read is [%s]\n",str);

    return 0;
}
           

程序2讀

/*
 * @Descripttion: 使用mmap完成沒有血緣關系的程序間通,傳入第一個參數為檔案名,若沒傳入則使用預設檔案"test.log"。
 * @version: 
 * @Author: Lzy
 * @Date: 2021-07-22 14:36:29
 * @LastEditors: Lzy
 * @LastEditTime: 2021-07-22 16:52:25
 */

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

int main(int argc, char **argv)
{
    int fd = 0;
    char *filename = NULL;
    if (argc > 1)
    {
        //傳入檔案名,使用提供檔案
        filename = argv[1];
    }
    else
    {
        //使用預設檔案名
        filename = "test.log";
    }

    printf("%s\n", filename);

    // int access(const char *pathname, int mode);
    fd = open(filename, O_RDWR | O_CREAT, 0777);
    if (fd == -1)
    {
        perror("open error:");
    }
    
    // ssize_t write(int fd, const void *buf, size_t count);
    //使檔案大小大于0,mmap函數要求
    write(fd, "1", strlen("1"));

    //擷取檔案大小
    struct stat statbuf;
    int ret = stat(filename, &statbuf);
    if (ret < 0)
    {
        perror("stat error:");
    }

    //    void *mmap(void *addr, size_t length, int prot, int flags,
    //               int fd, off_t offset);
    void *addr = mmap(NULL, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED)
    {
        perror("mmap error");
        return -1;
    }

    char str [20];
    memset(str,0,sizeof(str));
    memcpy(str,addr,strlen(addr));
    printf("read is [%s]\n",str);

    return 0;
}
           
  • 練習4:使用mmap函數建立匿名映射:mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);(父子程序分享)
/*
 * @Descripttion: 測試mmap匿名映射進行親緣關系通信(推薦)
 * @version: 
 * @Author: Lzy
 * @Date: 2021-07-22 10:43:24
 * @LastEditors: Lzy
 * @LastEditTime: 2021-07-22 14:34:15
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>

int main(int argc, char **argv)
{
    //    void *mmap(void *addr, size_t length, int prot, int flags,
    //               int fd, off_t offset);

    void *addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (addr == MAP_FAILED)
    {
        perror("mmap error");
        return -1;
    }

    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fork error");
        return -1;
    }
    else if (pid > 0)
    {
        memcpy(addr, "hello world!", strlen("hello world!"));
        wait(NULL);
    }
    else if (pid == 0)
    {
        char *p = (char *)addr;
        printf("%s", p);
    }

    return 0;
}