Linux進程間通信

    進程間通信:

              ·早期UNIX進程間通信方式:

                     無名管道(pipe)

                     有名管道(fifo)

                     信號(signal)

              ·System V IPC:

                     共享內存(share memory)

                     消息隊列(message queue)

                     信號燈集(semaphore set)

              ·套接字(socket)

 

·早期UNIX進程間通信方式:

     無名管道:

              特點:

                     1.只能用於具有親緣關係的進程之間的通信

                     2.單工的通信模式,具有固定的讀端和寫端

                     3.無名管道創建時會返回兩個文件描述符,分別用於讀寫管道

                     4.無名管道內的數據是存在內存中的,讀取後就會清空

              無名管道創建:

                     pipe.c

                     #include <unistd.h>

                     int pipe(int pfd[2]);

 

                     成功時返回0,失敗返回EOF

                     pfd包含兩個元素的整形數組,用來保存文件描述符

                     pfd[0]用於讀管道,pfd[1]用於寫管道

 

                     示例:

                            pipe.c

                            #include <stdio.h>

                            #include <stdlib.h>

                            #include <unistd.h>

                            #include <string.h>

                            #include <sys/types.h>

                            #include <sys/wait.h>



                            int main(void)

                            {

                                   pid_t pid1,pid2;

                                   char buf[32];

                                   int pfd[2];



                                   if (pipe(pfd) < 0)

                                   {

                                          perror("pipe");

                                          exit(-1);

                                   }



                                   if ((pid1 = fork()) < 0)

                                   {

                                          perror("fork");

                                          exit(-1);

                                   }

                                   else if(pid1 == 0)

                                   {

                                          strcpy(buf,"I'm process1");

                                          write(pfd[1],buf,32);

                                          exit(0);

                                   }

                                   else

                                   {

                                          if ((pid2 = fork()) < 0)

                                          {

                                                 perror("fork");

                                                 exit(-1);//表示程序異常退出。

                                          }

                                          else if (pid2 == 0)

                                          {

                                                 sleep(1);

                                                 strcpy(buf,"I'm process2");

                                                 write(pfd[1],buf,32);

                                          }

                                          else

                                          {

                                                 wait(NULL);

                                                 read(pfd[0],buf,32);

                                                 printf("%s\n",buf);

                                                 wait(NULL);

                                                 read(pfd[0],buf,32);

                                                 printf("%s\n",buf);

                                          }

                                   }



                                   return 0;

                            }

              寫端存在:

                     ·有數據          read返回實際讀取的字節數

                     ·無數據          進程阻塞

              寫端不存在:

                     ·有數據          read返回實際讀取的字節數

                     ·無數據          read返回0

              讀端存在:

                     ·有空間          write返回實際寫入的字節數

                     ·無空間          進程阻塞

              讀端不存在:

                     ·有空間

                     ·無空間

                     管道斷裂!

     有名管道:

              特點:

                     1.對應管道文件,可用於任意進程之間通信

                     2.打開管到時可指定讀寫方式

                     3.通過文件Io操作,內容存放在內存中

              有名管道創建:

                     #include <unistd.h>

                     #include<sys/types.h>

                     #include<sys/stat.h>

 

                     int mkfifo(const char *path,mode_t mode);

 

                     成功時返回0,失敗返回EOF

                     path創建的管道文件路徑

                     mode管道文件的權限,如0666

 

              示例:

                     create_fifo.c

                     read_fifo.c

                     write_fifo.c

 

     信號:

              信號機制:

                     ·信號是在軟件層次上對中斷機制的一種模擬,是一種異步通信方式

                     ·linux內核通過信號通知用戶進程,不同的信號類型代表不同的事件

                     ·linux對早期unix信號機制進行了擴展

                     ·進程對信號有不同的響應方式

                            1.缺省方式

                            2.忽略信號

                            3.捕捉信號    

                     常用信號:

                            詳情見有道雲 或 百度百科搜索 信號機制會有詳細說明

                                   進程間通信四—信號章節

       信號相關命令:

              kill [-signal] pid

              默認發送SIGTERM

              -sig可指定信號

              pid指定發送對象

 

              killall [-u user | prog]

              prog 指定進程名

              user 指定用戶名

 

              例子:

                     kill -9 6437

                     給進程組發信號

                     kill -9 -8126

                     給除init進程和當前進程外的其他進程發信號

                     kill -9 -1   

 

                     killall a.out

                     killall -u linux

       信號相關函數:

              kill / raise

#include <unistd.h>

#inclde <signal.h>


int kill(pid_t pid,int sig);

int raise(int sig);

              用於向任何進程組或進程發送信號。

              成功返回0,失敗返回EOF

              pid接收進程的進程號:

                     0代表同組進程         -1代表所有進程

                     sig信號類型

 

              alarm / pause

              int alarm(unsigned char seconds);

              成功時返回上個定時器的剩餘時間,失敗返回EOF

              seconds定時器的時間

              一個進程中只能設定一個定時器,時間到時產生SIGALRM

 

              int pasue(void);

              進程一直阻塞,直到被信號中斷

              被信號中斷後返回-1,errno爲EINTR

 

              示例:

                     alarm.c

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

int main()
{
	alarm(3);

	pause();

	printf("i have been waken up\n");
	return 0;
}

              設置信號響應方式:signal

                     #include <unistd.h>

                     #include <signal.h>

                     void (*signal(int signo,void(*handler)(int)))(int);

 

                     成功時返回原先的信號處理函數,失敗返回SIG_ERR

                     signo要設置的信號類型

                     handler指定的信號處理函數;SIG_DFL代表缺省方式;SIG_IGN代表忽略信號

                     只需要設置一次就一直有效

                    

                     示例:

                            signal.c

 #include <stdio.h>

 #include <signal.h>

#include <stdlib.h>

 #include <unistd.h>

 void handler(int signo)

 {

     if(signo == SIGINT)

     {

         printf("i have got SIGINT\n");

     }

    if(signo == SIGQUIT)
    {
        printf("i have got SIGQUIT\n");
    }

}


int main()
{

    signal(SIGINT,handler);
    signal(SIGQUIT,handler);
    while(1)
    {
         pause();
    }
    return 0;
}

System V IPC

       IPC對象包含:

              ·共享內存

              ·消息隊列

              ·信號燈集

       每個IPC對象有唯一的ID

       IPC對象創建後一直存在,直到被顯示地刪除

       每個IPC對象有一個關聯的KEY

       ipcs / ipcrm

 

       ipcs  命令   查看 System V 的IPC對象

       ipcrm 命令刪除一個或更多的消息隊列、信號量集或者共享內存標識。

 

       不同進程通過KEY值能夠打開同一個IPC對象

       key爲0   是私有對象

 

生成key值——ftok()函數

              #include <sys/types.h>

              #include <sys/ipc.h>

 

              key_t ftok(const cahr *path,int proj_id);

 

              成功時返回合法key值,失敗返回EOF

              path存在且可訪問的文件路徑

              proj_id用於生成key的數字,不能爲0

              通過路徑與proj_id兩者的第八位進行移位組合,生成key

             

1.共享內存:

  1. 共享內存是一種最爲高效的進程間通信方式,進程可以直接讀寫內存,而不需要任何數據拷貝
  2. 共享內存在內核空間創建,可被進程映射到用戶空間訪問,使用靈活
  3. 由於多個進程可同時訪問共享內存,因此需要同步和互斥機制的配合使用

 

 

共享內存使用步驟

  1. 創建/打開共享內存
  2. 映射共享內存,即把指定的共享內存映射到進程的地址空間用於訪問
  3. 讀寫共享內存
  4. 撤銷共享內存映射
  5. 刪除共享內存對象

1.共享內存創建—shmget

#include <sys/ipc.h>

       #include <sys/shm.h>

       int shmget(key_t key,int size,int shmflg);

 

成功返回共享內存的id,失敗返回EOF

key 和共享內存關聯的key,IPC_PRIVATE或ftok生成

shmflg 共享內存標誌位 IPC_CREAT | 0666

size 生成共享內存大小

shmflg:

        0:取共享內存標識符,若不存在則函數會報錯

           IPC_CREAT:當shmflg&IPC_CREAT爲真時,如果內核中不存在鍵值與key相等的共享內存,則新建一個共享內存;如果存在這樣的共享內存,返回此共享內存的標識符

           PC_CREAT|IPC_EXCL:如果內核中不存在鍵值 key相等的共享內存,則新建一個共享內存;如果存在這樣的共享內存則報錯

 

示例:

        private_shmget.c

        創建一個私有的共享內存,大小512字節,權限0666       

        #include <stdio.h>

        #include <sys/ipc.h>

        #include <sys/shm.h>

        #include <stdlib.h>

        int main()

        {



               int shmid;

               if((shmid = shmget(IPC_PRIVATE,512,066)) < 0)

               {

                      perror("shmget\n");

                      exit(-1);

               }

               return 0;

        }

        創建/打開一個和key關聯的共享內存,大小1024,權限0666

        key_shmget.c       

        #include <stdio.h>

        #include <stdlib.h>

        #include <sys/ipc.h>

        #include <sys/shm.h>

        int main()

        {

               key_t key;

               int shmid;

               if((key = ftok(".",'m')) == -1)

               {

                      perror("ftok");

                      exit(-1);

               }

               if((shmid = shmget(key,1024,IPC_CREAT|0666)) < 0)

               {

                      perror("shmget");

                      exit(-1);  

               }

        }

2.共享內存映射—shmat

#include <sys/ipc.h>

#include <sys/shm.h>

void *shmat(int shmid,const void *shmaddr,int shmflg);

 

成功返回映射後的地址,失敗返回(void*)-1

shmid 要映射的共享內存id

shmaddr 映射後的地址,NULL表示由系統自動映射

shmflg    標誌位0表示可讀寫;SHM_RDONLY表示只讀

3.共享內存撤銷映射—shmdt

#include <sys/ipc.h>

#include <sys/shm.c>

int shmdt(void *shmaddr);

 

成功返回0,失敗返回EOF

不適用共享內存時應撤銷映射

進程結束時自動撤銷

4.共享內存控制—shmctl

#include <sys/ipc.h>

#include <sys/shm.h>

int shmct(int shmid,int cmd,struct shmid_ds *buf);

 

成功時返回0,失敗返回EOF

shmid 要操作的共享內存id

cmd 要執行的操作 :

       IPC_STAT:得到共享內存的狀態,把共享內存的shmid_ds結構複製到buf中

       IPC_SET:改變共享內存的狀態,把buf所指的shmid_ds結構中的uid、gid、mode複製到共享內存的shmid_ds結構內

       IPC_RMID:刪除這片共享內存

buf 保存或設置共享內存屬性的地址

只是添加刪除標記,並不是真正刪除

共享內存注意事項

每塊共享內存大小有限制

       ipcs –l

       cat /proc/sys/kernel/shmmax

共享內存刪除的時間點

       shmctl(shmid,IPC_RMID,NULL)添加刪除標記

       nattach 變成0時真正刪除

2.消息隊列:

消息隊列是System V IPC對象的一種

消息隊列由消息隊列ID來唯一標識

消息隊列就是一個消息的列表。用戶可在消息隊列中添加消息,讀取消息等

消息隊列使用步驟

  1. 打開/創建消息隊列  msgget
  2. 向消息隊列發送消息  msgsnd
  3. 從消息隊列接收消息  msgrcv
  4. 控制消息隊列        msgctl

1.消息隊列創建/打開—msgget

#include <sys/ipc.h>

#include <sys/msg.h>

 

int msgget(key_t key,int msgflg);

 

成功時返回消息隊列的id,失敗返回EOF

key 和消息隊列關聯的key IPC_PRIVATE 或 ftok

msgflg 標誌位,消息隊列的建立標誌和存取權限。

IPC_CREAT | 0666

IPC_CREAT如果內核中沒有此隊列,則創建它。

IPC_EXCL當和IPC_CREAT一起使用時,如果隊列已經存在,則失敗。

       如果單獨使用IPC_CREAT,則msgget()要麼返回一個新創建的消息隊列的標識符,要麼返回具有相同關鍵字值的隊列的標識符。如果IPC_EXCL和IPC_CREAT一起使用,則msgget()要麼創建一個新的消息隊列,要麼如果隊列已經存在則返回一個失敗值-1。IPC_EXCL單獨使用是沒有用處的。

2.消息發送—msgsnd

#include <sys/ipc.h>

#include <sys/msg.h>

int msgsnd(int msgid,const void *msgp,size_t size,int msgflg);

 

成功時返回0,失敗返回-1

msgid 消息隊列id

msgp  消息緩衝區地址

size  消息正文長度

msgflg 標誌位0或IPC_NOWAIT

 

消息格式:

  1. 通信雙方首先定義好統一的消息格式
  2. 用戶根據應用需求定義結構體類型
  3. 首成員類型爲log,代表消息類型(正整數)
  4. 其他成員都屬於消息正文

3.消息接收

#include <sys/ipc.h>

#include <sys/msg.h>

int msgrcv(int msgid,void *msgp,size_t size,long msgtype,int msgflg);

 

成功時返回接收到的消息長度,失敗返回-1

msgid  消息隊列id

msgp   消息緩衝區地址

size   指定接收的消息長度

msgtype  指定接收的消息類型

msgflg   標誌欸0或IPC_NOWAIT

4.控制消息隊列

#include <sys/ipc.h>

#include <sys/msg.h>

int msgct(int msgid,int cmd,struct msqid_ds *buf);

 

成功返回0,失敗返回-1

msgid 消息隊列id

cmd   要執行的操作 IPC_STAT / IPC_SET / IPC_RMID

buf   存放消息隊列屬性的地址

示例:

要求:兩個進程通過消息隊列輪流將鍵盤輸入的字符串發送給對方,接收並打印對方發送到消息,輸入quit後退出

clientA.c

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <string.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>



typedef struct

{

       long mytype;

       char mtext[64];

}MSG;



#define LEN (sizeof(MSG) - sizeof(long))



#define TypeA 100

#define TypeB 200



int main()

{

       key_t key;

       int msgid;

       MSG buf;



       if((key = ftok(".",'q')) == -1)

       {

              perror("ftork");

              exit(-1);

       }

       if((msgid = msgget(key,IPC_CREAT | 0666)) < 0)

       {

              perror("msgget");

              exit(-1);

       }

       while(1)

       {

              buf.mytype = TypeB;

              printf("input > ");

              fgets(buf.mtext,64,stdin);

              msgsnd(msgid,&buf,LEN,0);

              if(strcmp(buf.mtext,"quit\n") == 0)break;  

              if(msgrcv(msgid,&buf,LEN,TypeA,0) < 0)  

              {

                     perror("msgrcv");

                     exit(-1);

              }

              printf("recv from clientB : %s",buf.mtext);

       }

       return 0;

}

client.c

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <string.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>



typedef struct

{

       long mytype;

       char mtext[64];

}MSG;



#define LEN (sizeof(MSG) - sizeof(long))



#define TypeA 100

#define TypeB 200



int main()

{

       key_t key;

       int msgid;

       MSG buf;



       if((key = ftok(".",'q')) == -1)

       {

              perror("ftork");

              exit(-1);

       }

       if((msgid = msgget(key,IPC_CREAT | 0666)) < 0)

       {

              perror("msgget");

              exit(-1);

       }

       while(1)

       {

              if(msgrcv(msgid,&buf,LEN,TypeB,0) < 0)   

              {

                     perror("msgrcv");

                     exit(-1);

              }

              printf("recv from clientA : %s",buf.mtext);



              buf.mytype = TypeA;

              printf("input > ");

              fgets(buf.mtext,64,stdin);

              msgsnd(msgid,&buf,LEN,0);

              if(strcmp(buf.mtext,"quit\n") == 0)break;

       }

       return 0;

}

3.信號燈

信號燈也叫信號量,用於進程/線程同步或互斥的機制

信號燈的類型:

  1. Posix 無名信號燈
  2. Posix 有名信號燈
  3. System V 信號燈

信號燈的含義:

       計數信號燈

System V IPC 信號燈特點:

  1. System V 信號燈是一個或多個計數信號燈的集合
  2. 可同時操作集合中的多個信號燈
  3. 申請多個資源時避免死鎖

System V 信號燈使用步驟:

       1.打開/創建信號燈    semget

       2.信號燈初始化           semctl

       3.P/V操作                   semop

       4.刪除信號燈            semctl

信號燈創建/打開—semget

#include <sys/ipc.h>

#include <sys/sem.h>

 

int semget(key_t key,int nsems,int semflg);

 

成功返回信號燈id,失敗返回-1

key 和消息隊列關聯的key IPC_PRIVATE 或 ftok

nsems 集合中包含的技術信號燈個數

semflg 標誌位 IPC_CREAT | 0666  IPC_EXCL

信號燈集合是從0開始

 

 

信號燈控制——semctl

#include <sys/ipc.h>

#include <sys/sem.h>

 

int semctl(int semid,int semnum,int cmd, /*union semun arg*/);

 

成功返回0,失敗返回EOF

semid 要操作的信號燈集id

semnum 要操作的集合中的信號燈編號

cmd 執行的操作 SETVAL IPC_RMID

cmd命令:

·IPC_STAT讀取一個信號量集的數據結構semid_ds,並將其存儲在semun中的buf參數中。

·IPC_SET設置信號量集的數據結構semid_ds中的元素ipc_perm,其值取自semun中的buf參數。

·IPC_RMID將信號量集從內存中刪除。

·GETALL用於讀取信號量集中的所有信號量的值。

·GETNCNT返回正在等待資源的進程數目。

·GETPID返回最後一個執行semop操作的進程的PID

·GETVAL返回信號量集中的一個單個的信號量的值。

·GETZCNT返回正在等待完全空閒的資源的進程數目。

·SETALL設置信號量集中的所有的信號量的值。

·SETVAL設置信號量集中的一個單獨的信號量的值。

 

union semun 取決於cmd:

       union semun {

              short val;          /*SETVAL用的值*/

              struct semid_ds* buf; /*IPC_STAT、IPC_SET用的semid_ds結構*/

              unsigned short* array; /*SETALL、GETALL用的數組值*/

             struct seminfo *buf;   /*爲控制IPC_INFO提供的緩存*/

      } arg;

 

信號燈P/V操作——semop

#include <sys/ipc.h>

#include <sys/sem.h>

int semop(int semid,struct sembuf *sops,unsigned nsops);

 

成功返回0,失敗返回-1

semid 要操作的信號燈集id

sops 描述對信號燈操作的結構體(數組)

nsops 要操作的信號燈個數

 

信號燈操作結構體——sembuf

struct sembuf

{

       short semnum; 

       short sem_op;

       short sem_flg;

};

semnum    信號燈編號

sem_op    -1:P操作,1:V操作

sem_flg     0/IPC_NOWAIT

 

示例:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <signal.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

#include <sys/sem.h>



#define N 64

#define READ 0

#define WRITE 1



union semun

{

       int val;

       struct semid_ds *buf;

       unsigned short *array;

       struct seminfo *bufs;   

};



void init_sem(int semid,int s[],int n)

{

       int i;

       union semun myun;

       for(i = 0;i < n;i++)

       {

              myun.val = s[i];

              semctl(semid,i,SETVAL,myun);

       }

}



void pv(int semid,int num,int op)

{

       struct sembuf buf;

       buf.sem_num = num;

       buf.sem_op = op;

       buf.sem_flg = 0;

       semop(semid,&buf,1);

}



int main()

{

       int shmid,semid,s[] = {0,1};

       pid_t pid;

       key_t key;        

       char *shmaddr;



       if((key = ftok(".",'s')) == -1)

       {

              perror("ftok");

              exit(-1);

       }

       if((shmid = shmget(key,N,IPC_CREAT | 0666)) < 0)

       {

              perror("shmget");

              exit(-1);

       }

       if((semid = semget(key,2,IPC_CREAT | 0666)) < 0)

       {

              perror("semget");

              goto _error1;

       }

       init_sem(semid,s,2);

       if((shmaddr = (char *)shmat(shmid,NULL,0)) == (char *)-1)

       {

              perror("shmaddr");

              goto _error2;

       }

       if((pid = fork()) < 0)

       {

              perror("fork");

              goto _error2;

       }

       else if(pid == 0)

       {

              char *p,*q;

              while(1)

              {

                     pv(semid,READ,-1);

                     p = q = shmaddr;

                     while(*q)

                     {

                            if(*q != ' ')     

                            {

                                   *p++ = *q;   

                            }            

                            q++;

                     }

                     *p = '\0';

                     printf("%s",shmaddr);

                     pv(semid,WRITE,1);

              }     

       }

       else

       {

              while(1)

              {

                     pv(semid,WRITE,-1);    

                     printf("input > ");

                     fgets(shmaddr,N,stdin);

                     if(strcmp(shmaddr,"quit\n") == 0)break;

                     pv(semid,READ,1);

              }     

              kill(pid,SIGUSR1);

       }

_error1:

       shmctl(shmid,IPC_RMID,NULL);

_error2:

       semctl(semid,0,IPC_RMID);

       return 0; 

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章