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