天天看點

Linux之程序間的通信

u# Linux之程序間的通信

程序間通信在實際項目中多多少少都會使用到,最常用的無名管道,有名管道,消息隊列,信号,信号量,共享記憶體等程序間的通信方式。其實後面網絡通信套位元組 socket的方式也可以歸為程序通行。

這些程序通信相關概念和手段在 linux 驅動中也會用到,雖然在驅動中稍微有一點點不同,名稱不同,接口函數略微不一樣,但是思想和手段都是差不多。

一、無名管道 pipe

從 UNIX 系統開始,無名管道的通信方式就存在,有點類似硬體中的序列槽,從最初的設計者定型之後,這種模型就一直延續到今天,說明無名管道當初設計的就極具科學性。不過無名管道有一定的局限性。

  • 第一:它是屬于半雙工的通信方式;
  • 第二:隻有具有“親緣關系”的的程序才能使用這種通信方式,也就是父程序和子程序之間。

1、使用 man 學習 pipe 函數

如下圖所示,使用指令“man 2 pipe”。有兩個類似的函數 pipe,pipe2。這裡再次提醒一句,教程中有些函數并沒有介紹到,作者對這些接口的介紹,有一個挑選的原則,**如果标準 C 能夠支援,那麼肯定是介紹标準 C,**因為學習之後在任何系統中都可以使用,移植也友善,然後就是 GNU 的進階版本支援的,最後盡量不會介紹淘汰的接口和函數。

Linux之程式間的通信

接着介紹一下 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、 編譯運作測試

運作程式如下。

Linux之程式間的通信

如上圖所示,運作之後,getchar 函數會接收從超級終端中輸入的字元,接收的函數 getchar在 write_data 函數中,寫進管道,被子程序讀取輸出到終端界面。

一、有名管道 fifo

無名管道隻能用于有親緣程序之間的通信,有名管道可以實作無親緣關系的通信。

有名管道 fifo 給檔案系統提供一個路徑,這個路徑和管道關聯,隻要知道這個管道路徑,就可以進行檔案通路,fifo 是指先進先出,也就是先寫入的資料,先讀出來。

1、使用 man 學習 mkfifo 函數

如下圖所示,使用指令“man 3 mkfifo”。

接着介紹一下 mkfifo 的用法。

Linux之程式間的通信
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建立文本檔案。

Linux之程式間的通信

接着看一下建立的 data.txt 檔案,其實是重複的“I want to study Linux!”。

Linux之程式間的通信

退出如上圖所示的 vi 文本編輯器,使用指令“./mnt/udisk/writepipe &”背景運作管道寫的程式 writepipe。

Linux之程式間的通信

接着如下圖所示,使用 linux 指令“jobs”檢視程式是否在運作。

Linux之程式間的通信

接着 5 秒延時看看這個 writepipe 是不是執行完了,使用 linux 指令“jobs;sleep 5;jobs”。運作中需要等待 5 秒,如下圖所示。

Linux之程式間的通信

如上圖所示,5 秒之後這個程式仍然在執行,說明是阻塞在管道寫哪裡,沒有讀就處于阻塞狀态。如下圖所示,可以看到 my_fifo 管道被創立了。

Linux之程式間的通信

接着使用指令“time ./mnt/udisk/readpipe”運作并計時從管道讀資料需要多長時間。

Linux之程式間的通信

如上圖所示,可以看到一共花費時間 0.26 秒。

接着使用指令“jobs”,寫程式已經結束。

Linux之程式間的通信

接着使用指令“ls -la”,如下圖所示,可以看到通過 FIFO 管道的資料大小為 2.3M。

Linux之程式間的通信

結合整個運作過程,2.3M 除于 0.26S,接近 10M/s。可見通過有名管道實作程序的通信速度非常快。

三、消息隊列 msg

消息隊列就是一個消息的連結清單。可以把消息看作一個記錄,具有特定的格式以及特定的優先級。對消息隊列有寫權限的程序可以向其中按照一定的規則添加新消息;對消息隊列有讀權限的程序則可以從消息隊列中讀走消息。

1、使用 man 學習 msgget 等函數

消息隊列主要有兩個函數 msgrcv 和 msgsnd,一個接收一個發送。

使用指令“man 2 msgrcv”,如下圖所示。

Linux之程式間的通信

分析一下 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,如下圖所示。

Linux之程式間的通信

注意到我們程式裡面msgflag = 0,如果沒資料的話,會一直阻塞,直到有資料可讀。

如下圖所示,運作發送程式,提醒使用者輸入字元。輸入除 end 以外其他任意字元串,都會接着提示繼續輸入,最後輸入 end,兩個程式都結束。

Linux之程式間的通信

四、信号 signal

信号用于處理異步事件,信号的通信方式了解起來還是有一定難度的。它既可以在一個程序内進行通信,發送信号給程序,又可以用于不同程序間的通信。

信号在驅動中應用比較廣泛,在應用中用到的多半是一些 linux 指令操作。

常見信号

  • SIGALRM:鬧鐘
  • SIGHUP:終端發出的結束信号
  • SIGINT:鍵盤的ctrl+c
  • SIGKILL:kill指令産生的信号
  • SIGSTOP:鍵盤ctrl+z

函數pause

用于捕捉程序挂起直到捕捉到信号

1、使用 man 學習 signal 等函數

函數 alarm,使用指令“man 2 alarm”如下圖所示。

Linux之程式間的通信

函數 signal,使用指令“man 2 signal”,如下圖所示。

Linux之程式間的通信

接着介紹一下 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程式如下。

Linux之程式間的通信

運作sigset程式如下。

Linux之程式間的通信

如上圖所示,

列印“Wait the signal SIGINT…”之後,輸入“Ctrl+c”。

列印“Please press Ctrl+c in 10 seconds…”之後再輸入“Ctrl+c”。

五、信号量 Semaphore

前面介紹的程序通信方式中,有一個問題,就是可能有其它多個程序通路同一個資源,為了提供一種排他性的通信,使用信号量可以解決這個問題。

1、 使用 man 學習 semget 等函數

如下圖所示,使用指令“man 2 semget”。

Linux之程式間的通信

接着介紹一下 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、 編譯運作測試

運作程式如下。

Linux之程式間的通信

六、共享記憶體 shmdata

共享記憶體是程序間通信中最簡單的方式之一。共享記憶體在各種程序間通信方式中具有最高的效率。因為系統核心沒有對通路共享記憶體進行同步,您必須提供自己的同步措施。解決這些問題的常用方法是通過使用信号量進行同步。

1、使用 man 學習 shmget 等函數

如下圖所示,使用指令“man 2 shmget”。

Linux之程式間的通信
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 如下。

Linux之程式間的通信

如下圖所示,接着使用指令“./mnt/udisk/shmwrite”運作 shmwrite,注意不要加“&”。

Linux之程式間的通信

如上圖所示,除了輸入字元串“end”,輸入其它字元串會一直提示輸入字元串,這些字元串被 shmread 讀取,并且在超級終端中列印。

繼續閱讀