1. 程式人生 > >【linux開發】程序間通訊命名管道-共享記憶體-記憶體對映-訊息佇列-訊號量

【linux開發】程序間通訊命名管道-共享記憶體-記憶體對映-訊息佇列-訊號量

程序間通訊命名管道-共享記憶體-記憶體對映-訊息佇列-訊號量
 在Unix平臺上,建立命名管道是建立了一個fifo檔案,和在shell下面用mkfifo命令的效果是一樣的。看起來這個管道檔案就是一個普通的檔案系統瓜掛載點,但是它只不過是作為一個名稱存在,實際的內容是一塊系統管理的共享記憶體。這個fifo檔案讀寫端同時處於open狀態,才能進行通訊。否則,一端open的話,會處於阻塞狀態。下面兩個例子程式,實現的內容是一樣的。父子程序間通過命名管道來通訊。當然,如果為了保證讀寫的順序,那麼就用wait()函式。
1. 最簡單的情況:程式輸出hello!
> cat r.C
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
int main(void){
  char FIFO[]=".myfifo";
  char msg[8]="hello!\n";
  char buf[8]={0};
  unlink(FIFO);
  mkfifo(FIFO,0666);
  if(fork()>0){
    int fd=open(FIFO,O_WRONLY);
    write(fd,msg,sizeof(msg));
    close(fd);
  }else{//child
    int fd=open(FIFO,O_RDONLY);
    read(fd,buf,sizeof(buf));
    write(STDOUT_FILENO,buf,sizeof(buf));
    close(fd);
  }
  return 0;
}

 
2. 稍微複雜點的情況,用wait()函式保證呼叫順序:
注意,wait之前,必須保證兩方的open都已經呼叫成功了。
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/wait.h>
int main(void){
  char FIFO[]=".myfifo";
  char msg[8]="hello!\n";
  char buf[8]={0};
  unlink(FIFO);
  mkfifo(FIFO,0666);
  pid_t pid=fork();
  int status;
  if(pid>0){//father
    int fd=open(FIFO,O_RDONLY);
    wait(&status);
    read(fd,buf,sizeof(buf));
    write(STDOUT_FILENO,buf,sizeof(buf));
    close(fd);
  }else{//child
    int fd=open(FIFO,O_WRONLY);
    write(fd,msg,sizeof(msg));
    close(fd);
  }
  return 0;
}
 
3. 更復雜的情況: 在兩個獨立的程序之間共享這個命名管道。注意s.C作為子程序r.C作為主程序。
>cat s.C(這個要被編譯成./s)


#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/wait.h>
int main(void){
  char FIFO[]=".myfifo";
  char msg[8]="hello!\n";
  int fd=open(FIFO,O_WRONLY);
  write(fd,msg,sizeof(msg));
  close(fd);
  return 0;
}
> cat r.C
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/wait.h>
int main(void){
  char FIFO[]=".myfifo";
  char buf[8]={0};
  unlink(FIFO);
  mkfifo(FIFO,0666);
  pid_t pid=fork();
  int status;
  if(pid>0){//father
    int fd=open(FIFO,O_RDONLY);
    wait(&status);
    read(fd,buf,sizeof(buf));
    write(STDOUT_FILENO,buf,sizeof(buf));
    close(fd);
  }else{//child
    execl("./s",NULL);
  }
  return 0;
}

 
輸出結果仍然是hello!

4. 如果不用命名管道,用共享記憶體或者記憶體對映的方式,效果是一樣的,因為管道本身就是一種共享記憶體。共享記憶體是系統管理的不需要經過檔案,記憶體對映則必須建立一箇中間檔案。相比較而言,Windows平臺上面共享記憶體和記憶體對映是一個函式CreateFileMapping的不同引數而已。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<sys/shm.h>
#include<sys/ipc.h>
#include<sys/mman.h>
struct p{
  int age;
  int height;
};
int main(void){
  key_t key=1234;
  int status;
  int shmfd=shmget(key,sizeof(p),0666|IPC_CREAT);
  if(shmfd==-1)exit(1);
  p* pt=(p*)shmat(shmfd,(void*)0,0);
  if(pt==(void*)-1)exit(2);
  int fd=open("my.txt",O_CREAT|O_RDWR|O_TRUNC,00777);
  p* pm=(p*)mmap(NULL,sizeof(p),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
  pid_t pid=fork();
  if(pid>0){//father
    wait(&status);
    printf("%d,%d\n",pt->age,pt->height);
    shmdt((char*)pt);
    shmctl(key,IPC_RMID,0);
    printf("%d,%d\n",pm->age,pm->height);
    msync((char*)pm,sizeof(p),MS_SYNC);
    munmap((char*)pm,sizeof(p));
    close(fd);
  }else{//child
    pt->age=20;
    pt->height=180;
    shmdt((char*)pt);
    pm->age=30;
    pm->height=170;
  }
  return 0;
}

 
程式執行的結果是,父程序讀取了子程序對共享記憶體的寫入內容,輸出:
20 180
30 170

5. 訊息佇列的用法也基本一樣。


#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<sys/ipc.h>
#include<sys/msg.h>
struct p{
  int age;
  int height;
};
int main(void){
  int msgfd=msgget(IPC_PRIVATE,0700);
  if(msgfd<0)exit(1);
  p pbuf={20,180};
  int status;
  pid_t pid=fork();
  if(pid>0){//father

    wait(&status);
    p rev;
    msgrcv(msgfd,&rev,sizeof(p),0,IPC_NOWAIT);
    printf("%d %d\n",rev.age,rev.height);
    msgctl(msgfd,IPC_RMID,NULL);
  }else{//child

    msgsnd(msgfd,&pbuf,sizeof(p),IPC_NOWAIT);
  }
  return 0;
}

 
程式輸出
20 180
6、訊號量
這裡介紹的訊號量函式要比前面介紹的用於執行緒的訊號量函式要更加通用,要想編寫通用的程式碼以確保程式對某個特定的資源具有獨佔式的訪問權是非常困難的,我們前面見過的一種可能的解決方法是使用帶O_EXCL標誌的open函式來建立鎖檔案,他提供了原子化的檔案建立辦法。荷蘭科學家提出的訊號量概念是在併發變成領域邁出的重要一步,如前面所討論的,訊號量是一個特殊的變數,他只取正整數值,並且程式對其訪問都是原子操作。
訊號量的一個更正式的定義:一個特殊變數,他值允許對他進行等待和傳送訊號這兩個操作,
P(訊號量變數):用於等待
V (訊號量變數) :用於傳送訊號
假設有一個訊號量sv,則
P(sv)如果SV的值大於0,就減去一,如果sv等於0就掛起這個程序的執行
V(sv)如果有其他程序因等待sc而被掛起,就讓他恢復執行,如果沒有程序因等待sv而被掛起就給他加1
linux的訊號量機制:
#include <sys/sem.h>
int semctl(int sem_id, int sem_num, int command, ...);
int semget(key_t key, int num_sems, int sem_flags);
int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);
semget函式的作用是建立一個新訊號量或獲得一個已有訊號量的鍵,第一個引數key是整數值,不相關的程序可以通過它訪問同一個訊號量,程式對所以訊號量的訪問都是間接的,他提供一個鍵再有系統生成一個相應的訊號量標示符,只有semget函式才直接使用key,所有其他訊號量函式都是使用由semget函式返回的訊號量標示符
There is a special semaphore key value, IPC_PRIVATE, that is intended to create a semaphore that only the creating process could access, but this rarely has any useful purpose. You should provide a unique,non-zero integer value for key when you want to create a new semaphore
num_sems引數指定需要的訊號量數目,他幾乎總是取值為1.
sem_flags是一組標誌,他與open函式的標誌非常相似,低端的9個bit是訊號量的許可權其作用類似於檔案訪問許可權,還可以和值IPC_CREAT做按位或操作來建立一個新訊號量。
semget函式在成功時返回一個正數,他就是其他訊號量函式將用到的訊號量標示符,失敗返回-1
semop函式用於改變訊號量的值,第一個引數sem_id是由semget返回的訊號量標示符,第二個引數sem_ops是指向一個結構體陣列的指標,每個陣列元素至少包含以下幾個成員:
struct sembuf {
short sem_num;
short sem_op;
short sem_flg;
}

第一個成員sem_num是訊號量編號,除非你需要使用一組訊號量,否則他一般取值0.sem_op的值是一次操作中需要改變的數值,通常只會用到兩個值一個是-1他等待訊號量可用,也就是p操作,一個是+1也就是v操作他傳送訊號量表示訊號現在可用,最後一個成員sem_flg通常被設定為SEM_UNDO,他將使得作業系統跟蹤當前程序對這個訊號量的修改情況,如果這個程序在沒有釋放訊號量的情況下終止,作業系統將自動釋放該程序持有的訊號量。
semop函式的呼叫時的一切動作時一次性完成的,這是為了避免出現因使用多個訊號量而可能發生的競爭現象。
semctl函式用來直接控制訊號量資訊,第一個引數sem_id是由semget返回的訊號量標示符。sem_num引數是訊號量編號,當需要用到成組的訊號量時,就要用到這個引數,他一般取值為0,表示這是第一個也是唯一的一個訊號量。command引數將要採取的動作,如果還有第四個引數,如果還有第四個引數它將會使一個union semun結構
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
}
semctl函式中的command引數可以設定許多不同的值,但只有下面介紹的兩個值最常用。
SETVAL: Used for initializing a semaphore to a known value. The value required is passed as the
val member of the union semun. This is required to set the semaphore up before it’s used for
the first time.
IPC_RMID: Used for deleting a semaphore identifier when it’s no longer required.

semctl函式將根據command引數的不同而返回不同的值,對於SETVAL和IPC_RMID成功時返回0,失敗時返回-1
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/sem.h>
#include "semun.h"
static int set_semvalue(void);
static void del_semvalue(void);
static int semaphore_p(void);
static int semaphore_v(void);
static int sem_id;
int main(int argc, char *argv[])
{
int i;
int pause_time;
char op_char = '0';
srand((unsigned int)getpid());
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);
}
op_char = 'X';
sleep(2);
}

for(i = 0; i < 10; i++) {
if (!semaphore_p()) exit(EXIT_FAILURE);
printf("%c", op_char);fflush(stdout);
pause_time = rand() % 3;
sleep(pause_time);
printf("%c", op_char);fflush(stdout);

if (!semaphore_v()) exit(EXIT_FAILURE);
pause_time = rand() % 2;
sleep(pause_time);
}
printf("\n%d - finished\n", getpid());
if (argc > 1) {
sleep(10);
del_semvalue();
}
exit(EXIT_SUCCESS);
}

static int set_semvalue(void)
{
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(void)
{
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(void)
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = -1;
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(void)
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = 1;
sem_b.sem_flg = SEM_UNDO;
if (semop(sem_id, &sem_b, 1) == -1) {
fprintf(stderr, "semaphore_v failed\n");
return(0);
}
return(1);
}