Linux系統編程5.消息管理

Linux 下的進程通信(IPC)
POSIX 無名信號量
System V 信號量
System V 消息隊列
System V 共享內存

1. POSIX無名信號量

PV操作是原子操作.也就是操作是不可以中斷的,在一定的時間內,只能夠有一個進程的代碼在 CPU 上面執行.

在系統當中,有時候爲了順利的使用和保護共享資源,提出了信號的概念.

假設使用一臺打印機,如果在同一時刻有兩個進程在向打印機輸出,那麼最終的結果會肯定是混亂的.

爲了處理這種情況,POSIX 標準提出了有名信號量和無名信號量的概念.

Linux 只實現了無名信號量

信號量的使用主要是用來保護共享資源,使的資源在一個時刻只有一個進程所擁有.爲此可以使用一個信號燈.當信號燈的值爲某個值的時候,就表明此時資源不可以使用.否則就表示可以使用.

POSIX 的無名信號量的函數有以下幾個:
#include <semaphore.h>;
int sem_init(sem_t *sem,int pshared,unsigned int value);
/*創建一個信號燈,並初始化其值爲 value.*/
/*pshared 決定了信號量能否在幾個進程間共享,Linux 還沒有實現進程間共享信號燈,所以這個值只能夠取 0.*/
int sem_destroy(sem_t *sem);
/*刪除信號燈*/
int sem_wait(sem_t *sem);
/*將阻塞進程,直到信號燈的值大於 0.這個函數返回的時候自動的將信號燈的值的-1*/
int sem_trywait(sem_t *sem);
/*不阻塞的,當信號燈的值爲 0 的時候返回 EAGAIN,表示以後重試.*/
int sem_post(sem_t *sem);
/*將信號燈的內容+1同時發出信號,喚醒等待的進程.*/
int sem_getvalue(sem_t *sem);
/*得到信號值*/

這幾個函數的使用相當簡單的.比如有一個程序要向一個系統打印機打印兩頁.

首先創建一個信號燈,並使其初始值爲 1,表示我們有一個資源可用.然後一個進程調用 sem_wait 由於這個時候信號燈的值爲 1,所以這個函數返回,打印機開始打印了,同時信號燈的值爲 0 了. 如果第二個進程要打印,調用 sem_wait 時候,由於信號燈的值爲 0,資源不可用,於是被阻塞了.

當第一個進程打印完成以後,調用 sem_post,信號燈的值爲 1 了,這個時候系統通知第二個進程,於是第二個進程的 sem_wait 返回.

第二個進程開始打印了.

可以使用線程來解決這個問題.

編譯包含上面這幾個函數的程序要加上 -lrt 選賢,以連接 librt.so 庫。

2. System V信號量

System V 信號量的函數主要有下面幾個.

#include <sys/types.h>;
#include <sys/ipc.h>;
#include <sys/sem.h>;
key_t ftok(char *pathname,char proj);
//根據 pathname 和 proj 來創建一個關鍵字.
int semget(key_t key,int nsems,int semflg);
// 創建一個信號量,成功時返回信號的 ID.
// key關鍵字,可以是ftok創建的或者IPC_PRIVATE表明由系統選用一個關鍵字. 
// nsems 表明創建的信號個數.
// semflg 是創建的權限標誌,和創建文件的標誌相同.
int semctl(int semid,int semnum,int cmd,union semun arg);
//對信號量進行一系列的控制.
//semid 是要操作的信號標誌,semnum 是信號的個數,cmd 是操作的命令.經常用的兩個值是: SETVAL(設置信號量的值)和 IPC_RMID(刪除信號燈).arg 是一個給 cmd 的參數.
int semop(int semid,struct sembuf *spos,int nspos);
/*對信號進行操作的函數.
semid 是信號標誌,
spos 是一個操作數組表明要進行什麼操作,
nspos 表明數組的個數. 

如果 sem_op 大於 0,那麼操作將 sem_op 加入到信號量的值中,並喚醒等待信號增加的進程. 

如果爲 0,當信號量的值是 0 的時候,函數返回,否則阻塞直到信號量的值爲 0. 

如果小於 0,函數判斷信號量的值加上這個負值.

如果結果爲 0 喚醒等待信號量爲 0 的進程,
如果小於 0 函數阻塞.
如果大於 0,那麼從信號量裏面減去這個值並返回*/
struct sembuf {
    short sem_num; /* 使用那一個信號 */
    short sem_op; /* 進行什麼操作 */
    short sem_flg; /* 操作的標誌 */
};

實例:

#include <stdio.h>;
#include <unistd.h>;
#include <limits.h>;
#include <errno.h>;
#include <string.h>;
#include <stdlib.h>;
#include <sys/stat.h>;
#include <sys/wait.h>;
#include <sys/ipc.h>;
#include <sys/sem.h>;
#define PERMS S_IRUSR|S_IWUSR
void init_semaphore_struct(struct sembuf *sem,int semnum,
int semop,int semflg)
{
    /* 初始化信號燈結構 */
    sem->sem_num=semnum;//使用哪一個信號
    sem->sem_op=semop;//進行什麼操作
    sem->sem_flg=semflg;//操作標誌
}
int del_semaphore(int semid)
{
/* 信號燈並不隨程序的結束而被刪除,如果沒刪除的話(將 1 改爲 0)
   可以用 ipcs 命令查看到信號燈,用 ipcrm 可以刪除信號燈的
*/
#if 1
    return semctl(semid,0,IPC_RMID);
#endif
}
int main(int argc,char **argv)
{
    char buffer[MAX_CANON],*c;
    int i,n;
    int semid,semop_ret,status;
    pid_t childpid;
    struct sembuf semwait,semsignal;/*semop函數的參數*/
/*------------------錯誤處理----------------------------*/
    if((argc!=2)||((n=atoi(argv[1]))<1))/*參數錯誤or參數太短*/
    {
        fprintf(stderr,"Usage: %s number\n\a",argv[0]);
        exit(1);
    }
    /* 使用 IPC_PRIVATE 表示由系統選擇一個關鍵字來創建 */
    /* 創建以後信號燈的初始值爲 0 */
    if((semid=semget(IPC_PRIVATE,1,PERMS))==-1)
    //有系統選擇一個關鍵字創建一個信號
    //創建信號燈出錯
    {
        fprintf(stderr,"[%d]: Acess Semaphore Error: %s\n\a",
        getpid(),strerror(errno));
        exit(1);
    }
/*------------------正確處理----------------------------*/
    /* semwait 是要求資源的操作(-1) */
    init_semaphore_struct(&semwait,0,-1,0);
    /* semsignal 是釋放資源的操作(+1) */
    init_semaphore_struct(&semsignal,0,1,0);
    /* 開始的時候有一個系統資源(一個標準錯誤輸出) */
    if(semop(semid,&semsignal,1)==-1)
    //信號操作出錯
    {
        fprintf(stderr,"[%d]: Increment Semaphore Error: %s\n\a",getpid(),strerror(errno));
        if(del_semaphore(semid)==-1)//刪除信號燈出錯
            fprintf(stderr,"[%d]: Destroy Semaphore Error: %s\n\a",getpid(),strerror(errno));
        exit(1);
    }
/* 創建一個進程鏈 */
    for(i=0;i<n;i++)
        if(childpid=fork()) 
            break;
    sprintf(buffer,"[i=%d]-->[Process=%d]-->[Parent=%d]-->[Child=%d]\n",i,getpid(),getppid(),childpid);
    c=buffer;
/* 這裏要求資源,進入原子操作 */
    while(((semop_ret=semop(semid,&semwait,1))==-1)&&(errno==EINTR));
    if(semop_ret==-1)
    {
        fprintf(stderr,"[%d]: 申請資源錯誤: %s\n\a",getpid(),strerror(errno));
    }
    else
    {
        fprintf(info,"[%d]: 申請資源成功: %s\n\a",
        getpid());
        while(*c!='\0')fputc(*c++,stderr);
        /* 原子操作完成,趕快釋放資源 */
        while(((semop_ret=semop(semid,&semsignal,1))==-1)&&(errno==EINTR));
        if(semop_ret==-1)
        fprintf(stderr,"[%d]: 釋放資源錯誤: %s\n\a",
        getpid(),strerror(errno));
    }
    /* 不能夠在其他進程反問信號燈的時候,刪除信號燈 */
    while((wait(&status)==-1)&&(errno==EINTR));
    /* 信號燈只能夠被刪除一次的 */
    if(i==1)
        if(del_semaphore(semid)==-1)
            fprintf(stderr,"[%d]: Destroy Semaphore Error: %s\n\a",getpid(),strerror(errno));
    exit(0);
}
信號燈的主要用途是保護臨界資源(在一個時刻只被一個進程所擁有)

3. SystemV 消息隊列

爲了便於進程之間通信,可以使用管道通信;SystemV 也提供了一些函數來實現進程的通信.這就是消息隊列。

#include <sys/types.h>; 
#include <sys/ipc.h>;
#include <sys/msg.h>;
int msgget(key_t key,int msgflg);
//msgget 函數和 semget 一樣,返回一個消息隊列的標誌

int msgsnd(int msgid,struct msgbuf *msgp,int msgsz,int msgflg);
int msgrcv(int msgid,struct msgbuf *msgp,int msgsz,
long msgtype,int msgflg);
//msgsnd 和 msgrcv 函數是用來進行消息通訊的

int msgctl(Int msgid,int cmd,struct msqid_ds *buf);
//msgctl 和 semctl 是對消息進行控制
struct msgbuf {
    long msgtype; /* 消息類型 */
    ....... /* 其他數據類型 */
}

msgid 是接受或者發送的消息隊列標誌.
msgp 是接受或者發送的內容.
msgsz 是消息的大小.
結構 msgbuf 包含的內容是至少有一個爲 msgtype.
其他的成分是用戶定義的.

對於發送函數 msgflg 指出緩衝區用完時候的操作.
接受函數指出無消息時候的處理.一般爲 0.
接收函數 msgtype 指出接收消息時候的操作.

如果 msgtype=0,接收消息隊列的第一個消息.
大於 0 接收隊列中消息類型等於這個值的第一個消息.
小於 0 接收消息隊列中小於或者等msgtype 絕對值的所有消息中的最小一個消息.

我們以一個實例來解釋進程通信.下面這個程序有 server 和 client 組成.先運行服務端後運行客戶端.
服務端 server.c

#include <stdio.h>;
#include <string.h>;
#include <stdlib.h>;
#include <errno.h>;
#include <unistd.h>;
#include <sys/types.h>;
#include <sys/ipc.h>;
#include <sys/stat.h>;
#include <sys/msg.h>;
#define MSG_FILE "server.c" //中間介質
#define BUFFER 255
#define PERM S_IRUSR|S_IWUSR
struct msgtype {
    long mtype;
    char buffer[BUFFER+1];
};
int main()
{
    struct msgtype msg;
    key_t key;
    int msgid;
    if((key=ftok(MSG_FILE,'a'))==-1)
    {
        fprintf(stderr,"Creat Key Error: %s\a\n",strerror(errno));
        exit(1);
    }
    if((msgid=msgget(key,PERM|IPC_CREAT|IPC_EXCL))==-1)
    {//和semget一樣返回一個msgid
        fprintf(stderr,"Creat Message Error: %s\a\n",strerror(errno));
        exit(1);
    }
    while(1)
    {
        msgrcv(msgid,&msg,sizeof(struct msgtype),1,0);
        fprintf(stderr,"Server Receive: %s\n",msg.buffer);
        msg.mtype=2;
        msgsnd(msgid,&msg,sizeof(struct msgtype),0);
    }
    exit(0);
}

客戶端(client.c)

#include <stdio.h>;
#include <string.h>;
#include <stdlib.h>;
#include <errno.h>;
#include <sys/types.h>;
#include <sys/ipc.h>;
#include <sys/msg.h>;
#include <sys/stat.h>;
#define MSG_FILE "server.c" //通信介質
#define BUFFER 255
#define PERM S_IRUSR|S_IWUSR

struct msgtype {
    long mtype;
    char buffer[BUFFER+1];
};
int main(int argc,char **argv)
{
    struct msgtype msg;
    key_t key;
    int msgid;
    if(argc!=2)
    {
        fprintf(stderr,"Usage: %s string\n\a",argv[0]);
        exit(1);
    }
    if((key=ftok(MSG_FILE,'a'))==-1)
    {
        fprintf(stderr,"Creat Key Error: %s\a\n",strerror(errno));
        exit(1);
    }
    if((msgid=msgget(key,PERM))==-1)
    {
        fprintf(stderr,"Creat Message Error:%s\a\n",strerror(errno));
        exit(1);
    }
    msg.mtype=1;
    strncpy(msg.buffer,argv[1],BUFFER);
    msgsnd(msgid,&msg,sizeof(struct msgtype),0);
    memset(&msg,'\0',sizeof(struct msgtype));
    msgrcv(msgid,&msg,sizeof(struct msgtype),2,0);
    fprintf(stderr,"Client receive: %s\n",msg.buffer);
    exit(0);
}
//注意服務端創建的消息隊列最後沒有刪除,我們要使用 ipcrm 命令來刪除的.

4. 共享內存

一個進程通信的方法是使用共享內存.SystemV 提供了以下幾個函數以實現共享內存

#include <sys/types.h>;
#include <sys/ipc.h>;
#include <sys/shm.h>;
int shmget(key_t key,int size,int shmflg);
/*size大小,shmflg只要用0代替就可以*/
void *shmat(int shmid,const void *shmaddr,int shmflg);
/*用來連接共享內存的*/
int shmdt(const void *shmaddr);
/*斷開共享內存*/
int shmctl(int shmid,int cmd,struct shmid_ds *buf);

在使用一個共享內存之前調用 shmat 得到共享內存的開始地址,使用結束以後使用 shmdt 斷開這個內存.

實例:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define PERM S_IRUSR|S_IWUSR
int main(int argc,char **argv)
{
    int shmid;
    char *p_addr,*c_addr;
    if(argc!=2)
    {
        fprintf(stderr,"Usage: %s\n\a",argv[0]);
        exit(1);
    }
    if((shmid=shmget(IPC_PRIVATE,1024,PERM))==-1)
    {
        fprintf(stderr,"Create Share Memory Error: %s\n\a",strerror(errno));
        exit(1);
    }
    if(fork())
    {
        p_addr=shmat(shmid,0,0);
        memset(p_addr,'\0',1024);
        strncpy(p_addr,argv[1],1024);
        exit(0);
    }
    else
    {
        c_addr=shmat(shmid,0,0);
        printf("Client get %s",c_addr);
        exit(0);
    }
}
/*父進程將參數寫入到共享內存,然後子進程把內容讀出來.最後我們要使用 ip
crm 釋放資源的.先用 ipcs 找出 ID 然後用 ipcrm shm ID 刪除.*/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章