u# Linux之程序間的通信
程序間通信在實際項目中多多少少都會使用到,最常用的無名管道,有名管道,消息隊列,信号,信号量,共享記憶體等程序間的通信方式。其實後面網絡通信套位元組 socket的方式也可以歸為程序通行。
這些程序通信相關概念和手段在 linux 驅動中也會用到,雖然在驅動中稍微有一點點不同,名稱不同,接口函數略微不一樣,但是思想和手段都是差不多。
一、無名管道 pipe
從 UNIX 系統開始,無名管道的通信方式就存在,有點類似硬體中的序列槽,從最初的設計者定型之後,這種模型就一直延續到今天,說明無名管道當初設計的就極具科學性。不過無名管道有一定的局限性。
- 第一:它是屬于半雙工的通信方式;
- 第二:隻有具有“親緣關系”的的程序才能使用這種通信方式,也就是父程序和子程序之間。
1、使用 man 學習 pipe 函數
如下圖所示,使用指令“man 2 pipe”。有兩個類似的函數 pipe,pipe2。這裡再次提醒一句,教程中有些函數并沒有介紹到,作者對這些接口的介紹,有一個挑選的原則,**如果标準 C 能夠支援,那麼肯定是介紹标準 C,**因為學習之後在任何系統中都可以使用,移植也友善,然後就是 GNU 的進階版本支援的,最後盡量不會介紹淘汰的接口和函數。
接着介紹一下 pipe 函數。
int pipe(int pipefd[2])
//參數 pipefd[0]:用于讀管道。
//參數 pipefd[1]:用于寫管道。
//傳回值:執行成功傳回 0,失敗傳回-1。
2、pipe 函數例程
編寫簡單的 pipe.c 檔案測試 pipe 函數。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
//程序讀函數
void read_data(int *);
//程序寫函數
void write_data(int *);
int main(int argc,char *argv[])
{
int pipes[2],rc;
pid_t pid;
rc = pipe(pipes); //建立管道
if(rc == -1){
perror("\npipes\n");
exit(1);
}
pid = fork(); //建立程序
switch(pid){
case -1:
perror("\nfork\n");
exit(1);
case 0:
read_data(pipes); //相同的pipes
default:
write_data(pipes); //相同的pipes
}
return 0;
}
//程序讀函數
void read_data(int pipes[])
{
int c,rc;
//由于此函數隻負責讀,是以将寫描述關閉(資源寶貴)
close(pipes[1]);
//阻塞,等待從管道讀取資料
//int 轉為 unsiged char 輸出到終端
while( (rc = read(pipes[0],&c,1)) > 0 ){
putchar(c);
}
exit(0);
}
//程序寫函數
void write_data(int pipes[])
{
int c,rc;
//關閉讀描述字
close(pipes[0]);
while( (c=getchar()) > 0 ){
rc = write( pipes[1], &c, 1); //寫入管道
if( rc == -1 ){
perror("Parent: write");
close(pipes[1]);
exit(1);
}
}
close( pipes[1] );
exit(0);
}
3、 編譯運作測試
運作程式如下。
如上圖所示,運作之後,getchar 函數會接收從超級終端中輸入的字元,接收的函數 getchar在 write_data 函數中,寫進管道,被子程序讀取輸出到終端界面。
一、有名管道 fifo
無名管道隻能用于有親緣程序之間的通信,有名管道可以實作無親緣關系的通信。
有名管道 fifo 給檔案系統提供一個路徑,這個路徑和管道關聯,隻要知道這個管道路徑,就可以進行檔案通路,fifo 是指先進先出,也就是先寫入的資料,先讀出來。
1、使用 man 學習 mkfifo 函數
如下圖所示,使用指令“man 3 mkfifo”。
接着介紹一下 mkfifo 的用法。
int mkfifo(const char *pathname, mode_t mode);
//參數*pathname:路徑名,管道名稱。
//參數 mode:管道的權限。
//傳回值:成功傳回 0,錯誤傳回-1。
2、函數例程
編寫簡單的 creatc.c 檔案測試 mkfifo 函數。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void filecopy(FILE *,char *);
int main(void)
{
FILE *fp1;
long int i = 100000;
char buf[] = "I want to study Linux!\n";
char *file1 = "data.txt";
printf("begin!\n");
if((fp1 = fopen(file1,"a+")) == NULL ){
printf("can't open %s\n",file1);
}
while(i--)
filecopy(fp1,buf);
fclose(fp1);
printf("over!\n");
return 0;
}
void filecopy(FILE *ifp,char *buf)
{
char c;
int i,j;
j = 0;
i = strlen(buf)-1;
while(i--){
putc(buf[j],ifp);
j++;
}
putc('\n',ifp);
}
編寫簡單的 readpipe.c 檔案測試對有名管道的讀。
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#include <string.h>
int main()
{
const char *fifo_name = "my_fifo";
int pipe_fd = -1;
int data_fd = -1;
int res = 0;
int open_mode = O_RDONLY;
char buffer[PIPE_BUF + 1];
int bytes_read = 0;
int bytes_write = 0;
//清空緩沖數組
memset(buffer, '\0', sizeof(buffer));
printf("Process %d opening FIFO O_RDONLY\n", getpid());
//以隻讀阻塞方式打開管道檔案,注意與fifowrite.c檔案中的FIFO同名
pipe_fd = open(fifo_name, open_mode);
//以隻寫方式建立儲存資料的檔案
data_fd = open("DataFormFIFO.txt", O_WRONLY|O_CREAT, 0644);
printf("Process %d result %d\n",getpid(), pipe_fd);
if(pipe_fd != -1)
{
do
{
//讀取FIFO中的資料,并把它儲存在檔案DataFormFIFO.txt檔案中
res = read(pipe_fd, buffer, PIPE_BUF);
bytes_write = write(data_fd, buffer, res);
bytes_read += res;
}while(res > 0);
close(pipe_fd);
close(data_fd);
}
else
exit(EXIT_FAILURE);
printf("Process %d finished, %d bytes read\n", getpid(), bytes_read);
exit(EXIT_SUCCESS);
}
編寫簡單的 writepipe.c 檔案測試對有名管道的寫。
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
int main()
{
const char *fifo_name = "my_fifo";
char *file1 = "data.txt";
int pipe_fd = -1;
int data_fd = -1;
int res = 0;
const int open_mode = O_WRONLY;
int bytes_sent = 0;
char buffer[PIPE_BUF + 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());
//以隻寫阻塞方式打開FIFO檔案,以隻讀方式打開資料檔案
pipe_fd = open(fifo_name, open_mode);
data_fd = open(file1, O_RDONLY);
printf("Process %d result %d\n", getpid(), pipe_fd);
if(pipe_fd != -1)
{
int bytes_read = 0;
//向資料檔案讀取資料
bytes_read = read(data_fd, buffer, PIPE_BUF);
buffer[bytes_read] = '\0';
while(bytes_read > 0)
{
//向FIFO檔案寫資料
res = write(pipe_fd, buffer, bytes_read);
if(res == -1)
{
fprintf(stderr, "Write error on pipe\n");
exit(EXIT_FAILURE);
}
//累加寫的位元組數,并繼續讀取資料
bytes_sent += res;
bytes_read = read(data_fd, buffer, PIPE_BUF);
buffer[bytes_read] = '\0';
}
close(pipe_fd);
close(data_fd);
}
else
exit(EXIT_FAILURE);
printf("Process %d finished\n", getpid());
exit(EXIT_SUCCESS);
}
3、 編譯運作測試
首先用createc建立文本檔案。
接着看一下建立的 data.txt 檔案,其實是重複的“I want to study Linux!”。
退出如上圖所示的 vi 文本編輯器,使用指令“./mnt/udisk/writepipe &”背景運作管道寫的程式 writepipe。
接着如下圖所示,使用 linux 指令“jobs”檢視程式是否在運作。
接着 5 秒延時看看這個 writepipe 是不是執行完了,使用 linux 指令“jobs;sleep 5;jobs”。運作中需要等待 5 秒,如下圖所示。
如上圖所示,5 秒之後這個程式仍然在執行,說明是阻塞在管道寫哪裡,沒有讀就處于阻塞狀态。如下圖所示,可以看到 my_fifo 管道被創立了。
接着使用指令“time ./mnt/udisk/readpipe”運作并計時從管道讀資料需要多長時間。
如上圖所示,可以看到一共花費時間 0.26 秒。
接着使用指令“jobs”,寫程式已經結束。
接着使用指令“ls -la”,如下圖所示,可以看到通過 FIFO 管道的資料大小為 2.3M。
結合整個運作過程,2.3M 除于 0.26S,接近 10M/s。可見通過有名管道實作程序的通信速度非常快。
三、消息隊列 msg
消息隊列就是一個消息的連結清單。可以把消息看作一個記錄,具有特定的格式以及特定的優先級。對消息隊列有寫權限的程序可以向其中按照一定的規則添加新消息;對消息隊列有讀權限的程序則可以從消息隊列中讀走消息。
1、使用 man 學習 msgget 等函數
消息隊列主要有兩個函數 msgrcv 和 msgsnd,一個接收一個發送。
使用指令“man 2 msgrcv”,如下圖所示。
分析一下 msgrcv 和 msgsnd 函數。
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//參數 msqid:消息隊列的辨別碼。
//參數*msgp:指向消息緩沖區的指針,此位置用來暫時存儲發送和接收的消息,是一個使用者可定義的通用結構。
//參數 msgsz:消息的長短。
//參數 msgflg:标志位。
//傳回值:成功傳回 0,錯誤傳回-1。
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
//參數 msqid:消息隊列的辨別碼。
//參數*msgp:指向消息緩沖區的指針。
//參數 msgsz:消息的長短
//參數 msgflg:标志位。
//傳回值:成功傳回資料長度,錯誤傳回-1。
結構體 msgp,是一個标準的通用結構,如下所示。
struct msgstru{
long mtype; //大于 0
char mtext[nbyte];
};
還有一個函數 msgget 需要介紹,用來擷取與某個鍵“key”關聯的消息隊列辨別。
int msgget(key_t key, int msgflg):
//參數“key”:消息隊列關聯的鍵。
//參數“msgflg”:消息隊列的建立标志和存取權限。IPC_CREAT 如果核心中沒有此隊列,則建立它;
//IPC_EXCL 當和 IPC_CREAT 一起使用時,如果隊列已經存在,則失敗。
//傳回值:執行成功則傳回消息隊列的辨別符,否則傳回-1。
2、函數例程
編寫簡單的 msgsend.c 和 msgreceive.c 檔案實作消息隊列。
檔案 msgsend.c,如下所示。
#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);
}
檔案 msgreceive.c,如下所示。
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/msg.h>
struct msg_st
{
long int msg_type;
char text[BUFSIZ];
};
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);
}
3、 編譯運作測試
先在背景運作接收程式 msgreceive,如下圖所示。
注意到我們程式裡面msgflag = 0,如果沒資料的話,會一直阻塞,直到有資料可讀。
如下圖所示,運作發送程式,提醒使用者輸入字元。輸入除 end 以外其他任意字元串,都會接着提示繼續輸入,最後輸入 end,兩個程式都結束。
四、信号 signal
信号用于處理異步事件,信号的通信方式了解起來還是有一定難度的。它既可以在一個程序内進行通信,發送信号給程序,又可以用于不同程序間的通信。
信号在驅動中應用比較廣泛,在應用中用到的多半是一些 linux 指令操作。
常見信号
- SIGALRM:鬧鐘
- SIGHUP:終端發出的結束信号
- SIGINT:鍵盤的ctrl+c
- SIGKILL:kill指令産生的信号
- SIGSTOP:鍵盤ctrl+z
函數pause
用于捕捉程序挂起直到捕捉到信号
1、使用 man 學習 signal 等函數
函數 alarm,使用指令“man 2 alarm”如下圖所示。
函數 signal,使用指令“man 2 signal”,如下圖所示。
接着介紹一下 alarm 和 signal 的用法。
unsigned int alarm(unsigned int seconds);
//參數 seconds:鬧鐘的時間,機關為秒。
//傳回值:成功傳回 0 或者傳回剩餘時間;錯誤傳回-1。
//根據文檔,可以看到這個函數會在seconds時間内給正在運作的程序安排一個SIGNALRM信号
sighandler_t signal(int signum, sighandler_t handler);
//參數 signum:等待的信号。
//參數 handler:信号到來之後,觸發的處理方式。
//傳回值:成功傳回 handler的傳回值,錯誤傳回error值。
2、signal 例程
編寫簡單的 sig_hello.c 檔案測試信号函數 signal 以及鬧鐘 alarm 函數。
#include<unistd.h>
#include<stdio.h>
#include<signal.h>
void handler()
{
printf("hello\n");
}
int main(void)
{
int i;
signal(SIGALRM, handler);
alarm(5);
for(i=1;i<7;i++){
printf("sleep %d....\n",i);
sleep(1);
}
return 0;
}
除了鬧鐘可以産生信号之外,在終端輸入“Ctrl+c”也會産生信号。
編寫簡單的 sigset.c 檔案測試終端産生的信号。
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
void handler(int sig)
{
printf("Handler the signal %d\n", sig);
}
int main(void)
{
sigset_t sigset;//用于記錄屏蔽字
sigset_t ign;//用于記錄被阻塞的信号集
struct sigaction act;
//清空信号集
sigemptyset(&sigset); //初始化信号集
sigemptyset(&ign);
//向信号集中添加信号SIGINT
sigaddset(&sigset, SIGINT);
//設定處理函數和信号集
act.sa_handler = handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGINT, &act, 0);
printf("Wait the signal SIGINT...\n");
pause();//挂起程序,等待信号
//設定程序屏蔽字,在本例中為屏蔽SIGINT
sigprocmask(SIG_SETMASK, &sigset, 0);
printf("Please press Ctrl+c in 10 seconds...\n");
sleep(10);
//測試SIGINT是否被屏蔽
sigpending(&ign);
if(sigismember(&ign, SIGINT))
printf("The SIGINT signal has ignored\n");
//在信号集中删除信号SIGINT
sigdelset(&sigset, SIGINT);
printf("Wait the signal SIGINT...\n");
//将程序的屏蔽字重新設定,即取消對SIGINT的屏蔽
//并挂起程序
sigsuspend(&sigset);
printf("The app will exit in 5 seconds!\n");
sleep(5);
exit(0);
}
3、 編譯運作測試
運作sig_hello程式如下。
運作sigset程式如下。
如上圖所示,
列印“Wait the signal SIGINT…”之後,輸入“Ctrl+c”。
列印“Please press Ctrl+c in 10 seconds…”之後再輸入“Ctrl+c”。
五、信号量 Semaphore
前面介紹的程序通信方式中,有一個問題,就是可能有其它多個程序通路同一個資源,為了提供一種排他性的通信,使用信号量可以解決這個問題。
1、 使用 man 學習 semget 等函數
如下圖所示,使用指令“man 2 semget”。
接着介紹一下 semget 函數的用法。
int semget(key_t key, int nsems, int semflg);
//參數 key:一個用來允許不相關的程序通路相同信号量的整數值。
//參數 nsems:需要的信号量數目。這個值通常總是 1。
//參數 semflg:标記集合,與 open 函數的标記十分類似。
//傳回值:成功傳回辨別符,用于其它信号函數,錯誤傳回-1
2、Semaphore 例程
編寫簡單的 seml.c 檔案測試 seml 函數。
#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;
}
3、 編譯運作測試
運作程式如下。
六、共享記憶體 shmdata
共享記憶體是程序間通信中最簡單的方式之一。共享記憶體在各種程序間通信方式中具有最高的效率。因為系統核心沒有對通路共享記憶體進行同步,您必須提供自己的同步措施。解決這些問題的常用方法是通過使用信号量進行同步。
1、使用 man 學習 shmget 等函數
如下圖所示,使用指令“man 2 shmget”。
int shmget(key_t key, size_t size, int shmflg);
//參數 key:建立新的共享記憶體對象
//參數 size:建立立的記憶體大小
//參數 shmflg:辨別符
//傳回值:成功 shmget 傳回一個共享記憶體辨別符或建立一個共享記憶體對象,錯誤傳回-1。
其它函數,如下所示。
void *shmat(int shmid, const void *shmaddr, int shmflg)
//參數 shmid:共享記憶體辨別符
//參數 shmaddr:指定共享記憶體出現在程序記憶體位址的什麼位置,直接指定為 NULL 讓核心自己決定一個合适的位址位置
//參數 shmflg :SHM_RDONLY,為隻讀模式,其他為讀寫模式
//傳回值:成功傳回共享的記憶體位址,否則傳回-1。
int shmdt(const void *shmaddr)
//參數 shmaddr:連接配接的共享記憶體的起始位址。
//傳回值:成功傳回 0,錯誤傳回-1。
int shmctl(int shmid, int cmd, struct shmid_ds *buf)
//參數 shmid:共享記憶體辨別符
//參數 cmd IPC_RMID:删除這片共享記憶體
//參數 buf:共享記憶體管理結構體
//傳回值:成功傳回 0,錯誤傳回-1。
2、shmdata 例程
先編寫簡單的 shmdata.h,記憶體讀和寫都需要用到的機構。
#ifndef _SHMDATA_H_HEADER
#define _SHMDATA_H_HEADER
#define TEXT_SZ 2048
struct shared_use_st
{
//作為一個标志,非0:表示可讀,0表示可寫
int written;
//記錄寫入和讀取的文本
char text[TEXT_SZ];
};
#endif
編寫簡單的 shmwrite.c 檔案測試寫函數。
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
#include "shmdata.h"
int main(void)
{
int running = 1;
void *shm = NULL;
struct shared_use_st *shared = NULL;
char buffer[BUFSIZ + 1];//用于儲存輸入的文本
int shmid;
//建立共享記憶體
shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);
if(shmid == -1)
{
fprintf(stderr, "shmget failed\n");
exit(EXIT_FAILURE);
}
//将共享記憶體連接配接到目前程序的位址空間
shm = shmat(shmid, (void*)0, 0);
if(shm == (void*)-1)
{
fprintf(stderr, "shmat failed\n");
exit(EXIT_FAILURE);
}
printf("Memory attached at %p\n", shm);
//設定共享記憶體
shared = (struct shared_use_st*)shm;
while(running)//向共享記憶體中寫資料
{
//資料還沒有被讀取,則等待資料被讀取,不能向共享記憶體中寫入文本
while(shared->written == 1)
{
sleep(1);
printf("Waiting...\n");
}
//向共享記憶體中寫入資料
printf("Enter some text: ");
fgets(buffer, BUFSIZ, stdin);
strncpy(shared->text, buffer, TEXT_SZ);
//寫完資料,設定written使共享記憶體段可讀
shared->written = 1;
//輸入了end,退出循環(程式)
if(strncmp(buffer, "end", 3) == 0)
running = 0;
}
//把共享記憶體從目前程序中分離
if(shmdt(shm) == -1)
{
fprintf(stderr, "shmdt failed\n");
exit(EXIT_FAILURE);
}
sleep(2);
exit(EXIT_SUCCESS);
}
編寫簡單的 shmread.c 檔案測試寫函數。
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/shm.h>
#include "shmdata.h"
int main(void)
{
int running = 1;//程式是否繼續運作的标志
void *shm = NULL;//配置設定的共享記憶體的原始首位址
struct shared_use_st *shared;//指向shm
int shmid;//共享記憶體辨別符
//建立共享記憶體
shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);
if(shmid == -1)
{
fprintf(stderr, "shmget failed\n");
exit(EXIT_FAILURE);
}
//将共享記憶體連接配接到目前程序的位址空間
shm = shmat(shmid, 0, 0);
if(shm == (void*)-1)
{
fprintf(stderr, "shmat failed\n");
exit(EXIT_FAILURE);
}
printf("\nMemory attached at %p\n", shm);
//設定共享記憶體
shared = (struct shared_use_st*)shm;
shared->written = 0;
while(running)//讀取共享記憶體中的資料
{
//沒有程序向共享記憶體定資料有資料可讀取
if(shared->written != 0)
{
printf("You wrote: %s", shared->text);
sleep(rand() % 3);
//讀取完資料,設定written使共享記憶體段可寫
shared->written = 0;
//輸入了end,退出循環(程式)
if(strncmp(shared->text, "end", 3) == 0)
running = 0;
}
else//有其他程序在寫資料,不能讀取資料
sleep(1);
}
//把共享記憶體從目前程序中分離
if(shmdt(shm) == -1)
{
fprintf(stderr, "shmdt failed\n");
exit(EXIT_FAILURE);
}
//删除共享記憶體
if(shmctl(shmid, IPC_RMID, 0) == -1)
{
fprintf(stderr, "shmctl(IPC_RMID) failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
3、 編譯運作測試
運作程式 shmread 如下。
如下圖所示,接着使用指令“./mnt/udisk/shmwrite”運作 shmwrite,注意不要加“&”。
如上圖所示,除了輸入字元串“end”,輸入其它字元串會一直提示輸入字元串,這些字元串被 shmread 讀取,并且在超級終端中列印。