Linux C進程間通訊

1、基本概念css

  1.進程組:屬於同一個登陸shell的進程linux

  2.內核和超級用戶能夠發送信號到全部進程shell

    普通用戶:只能向那些有相同UID,GID的進程發送信號,或者是一個進程組的進程發送信號數組

  3.管道:單向的,先入先出,固定大小的數據流。進程讀取空管道時,直到有數據輸入前,進程阻塞;管道已滿,進程寫入時,進程阻塞服務器

   命名管道 FIFOS 經過mkfifo建立數據結構

  4.System V中三種進程通訊機制:消息隊列、信號量、共享內存函數

   5.臨界區是一段代碼,任意時刻只能由一個進程之星它ui

6.進程收到信號的處置方式:1)忽略信號2)執行處理信號的函數3)暫停進程的執行4)重啓剛纔暫停的進程5)採用系統默認的操做this

  7.信號 SIGHUP 掛斷控制終端 SIGINT 控制終端中斷鍵按下SIGKILL 刪除一個或一組進程,信號不能別忽略  SIGSTOP 暫停進程spa

8.內核轉儲:終止進程時沒留下一個稱爲core的文件,存儲當時進程內存中的內容,用於過後查錯。

 

二.註冊信號調用函數

signal()

#include <signal.h>

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

第一個參數指定信號的值,第二個參數指定針對前面信號值的處理,能夠忽略該信號(參數設爲SIG_IGN);能夠採用系統默認方式處理信號(參數設爲SIG_DFL);也能夠本身實現處理方式(參數指定一個函數地址)。

若是signal()調用成功,返回最後一次爲安裝信號signum而調用signal()時的handler值;失敗則返回-1。

 

例如:

void sigcatcher(int signum);

int main()
{
  char buffer1[100],buffer2[100];
  int i;
  f(signal(SIGTERM,&sigcatcher)==-1)
  {
    printf("Couldn't resister signal handleer!\n");
    exit(1);
  }
  printf("pid if this process is :%d \n",getpid());
  printf("Please input:\n");
  for(;;)
  {
  fgets(buffer1,sizeof(buffer1),stdin);
  for(i=0;i<100;i++)
  {
  if(buffer1[i]>=97 && buffer1[i]<=122)
  buffer2[i]=buffer1[i]-32;
  else
  buffer2[i]=buffer1[i];
  }
  printf("Your inout is %s \n",buffer2);
  }
  exit(0);
}

void sigcatcher(int signum)
{
  printf("catch signal SIZETERM.\n");//在終端另外一個窗口輸入kill 進程號就會結束進程
  exit(0);

  }

 

2.

#include <signal.h>

int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));

sigaction函數用於改變進程接收到特定信號後的行爲。該函數的第一個參數爲信號的值,能夠爲除SIGKILL及SIGSTOP外的任何一個特定有效的信號(爲這兩個信號定義本身的處理函數,將致使信號安裝錯誤)。第二個參數是指向結構sigaction的一個實例的指針,在結構sigaction的實例中,指定了對特定信號的處理,能夠爲空,進程會以缺省方式對信號處理;第三個參數oldact指向的對象用來保存返回的原來對相應信號的處理,可指定oldact爲NULL。若是把第2、第三個參數都設爲NULL,那麼該函數可用於檢查信號的有效性。

sigaction結構定義以下:

struct sigaction {

         __sighandler_t _sa_handler;

            void (*_sa_sigaction)(int,struct siginfo *, void *);

            sigset_t sa_mask;

            unsigned long sa_flags;

}

 

例如:

  struct sigaction act;
   act.sa_handler=sighandler;
  sigemptyset(&act.sa_mask);
  act.sa_flags=0;
  if(sigaction(SIGTERM,&act,NULL)==-1)
  {
    printf("Couldn't resister signal handleer!\n");
    exit(1);
  }

實現和signal同樣的功能

 

3.信號集

信號集被定義爲一種數據類型:

typedef struct {

                       unsigned long sig[_NSIG_WORDS];

} sigset_t

信號集用來描述信號的集合,每一個信號佔用一位。Linux所支持的全部信號能夠所有或部分的出如今信號集中,主要與信號阻塞相關函數配合使用。下面是爲信號集操做定義的相關函數:

 

#include <signal.h>

int sigemptyset(sigset_t *set);

int sigfillset(sigset_t *set);

int sigaddset(sigset_t *set, int signum)

int sigdelset(sigset_t *set, int signum);

int sigismember(const sigset_t *set, int signum);

sigemptyset(sigset_t *set)初始化由set指定的信號集,信號集裏面的全部信號被清空;

sigfillset(sigset_t *set)調用該函數後,set指向的信號集中將包含linux支持的64種信號;

sigaddset(sigset_t *set, int signum)在set指向的信號集中加入signum信號;

sigdelset(sigset_t *set, int signum)在set指向的信號集中刪除signum信號;

sigismember(const sigset_t *set, int signum)斷定信號signum是否在set指向的信號集中。

信號集操做

1. int  sigprocmask(int  how,  const  sigset_t *set, sigset_t *oldset));  oldset非空返回當前信號掩碼;set非空,根據how設定掩碼;set爲空沒有意義 執行成功返回0,不然返回-1

      SIG_BLOCK 在進程當前阻塞信號集中添加set指向信號集中的信號

     SIG_UNBLOCK 若是進程阻塞信號集中包含set指向信號集中的信號,則解除對該信號的阻塞

      SIG_SETMASK 更新進程阻塞信號集爲set指向的信號集

#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void pr_mask(int signum)
{
sigset_t sigset;
if(sigprocmask(0,NULL,&sigset)<0)//返回當前進程的信號掩碼,到sigset中
{
   printf("sigprocmask error\n");
   exit(0);
}
if(sigismember(&sigset,SIGINT))
      printf("SIGINT\n");
if(sigismember(&sigset,SIGTERM))
{
    printf("SIGTERM\n");
exit(0);
}
}

 

if(signal(SIGINT,&pr_mask)==-1)//pr_mask 收到信號後處理函數
{
    printf("Couldn't resister signal handleer!\n");
    exit(1);
}
if(signal(SIGTERM,&pr_mask)==-1)
{
    printf("Couldn't resister signal handleer!\n");
    exit(1);
}

 

2.sigpending(sigset_t *set))得到當前已遞送到進程,卻被阻塞的全部信號,在set指向的信號集中返回結果。

 

static void sig_quit(int);

int main()
{
  sigset_t newmask,oldmask,pendmask;
  if(signal(SIGQUIT,&sig_quit)==-1)  //註冊信號SIGQUIT
  {
  printf("couldn't register signal handler for SIGQUIT.\n");
  exit(1);
  }
  sigemptyset(&newmask);//清空信號集
  sigaddset(&newmask,SIGQUIT);//添加SIGQUIT信號


  if(sigprocmask(SIG_BLOCK,&newmask,&oldmask)<0)//保存當前信號集
  {
    printf("SIG_BLOCK error.\n");
    exit(2);
  }
  sleep(5);
  if(sigpending(&pendmask)<0)//檢測是否有掛起信號
  {
  printf("sigpend error\n");
  exit(3);
  }
  if(sigismember(&pendmask,SIGQUIT))//查看SIGQUIT是否在掛起信號集中
    printf("SIGQUIT pending\n");
  if(sigprocmask(SIG_SETMASK,&oldmask,NULL)<0)//消除信號的阻塞
  {
    printf("SIG_SETMASK error.\n");
    exit(4);
  }
  printf("SIGQUIT unblocked.\n");
    sleep(5);
    exit(0);
}

static void sig_quit(int signum)
{
    printf("catch SIGQUIT\n");
  if(signal(SIGQUIT,SIG_DFL)==-1)
   printf("Couldn't reset SIGQUIT!\n");
return;
}

 

 

3.sigsuspend(const sigset_t *mask))用於在接收到某個信號以前, 臨時用mask替換進程的信號掩碼, 並暫停進程執行,直到收到信號爲止。sigsuspend 返回後將恢復調用以前的信號掩碼。信號處理函數完成後,進程將繼續執行。該系統調用始終返回-1,並將errno設置爲EINTR。

while(quitflag==0)

   sigsuspend(&zeromask);   //直到quitflag變爲1時,進程甦醒,從新執行

 

 

三.發送信號

1.raise 函數

int raise(int signum) 想一個進程自身發送信號,成功返回0,不然返回-1

2.kill 

int kill(pid_t pid,int signo) 成功返回0 錯誤返回1

 

該系統調用能夠用來向任何進程或進程組發送任何信號。參數pid的值爲信號的接收進程

pid>0 進程ID爲pid的進程

pid=0 同一個進程組的進程

pid<0 pid!=-1 進程組ID爲 -pid的全部進程

pid=-1 除發送進程自身外,全部進程ID大於1的進程

 

4、管道

管道是Linux支持的最初Unix IPC形式之一,具備如下特色:
  A. 管道是半雙工的,數據只能向一個方向流動;
  B. 須要雙工通訊時,須要創建起兩個管道;
  C. 只能用於父子進程或者兄弟進程之間(具備親緣關係的進程);
  D. 單獨構成一種獨立的文件系統:管道對於管道兩端的進程而言,就是一個文件,
     但它不是普通的文件,它不屬於某種文件系統,而是自立門戶,
     單獨構成一種文件系統,而且只存在與內存中。
 
數據的讀出和寫入:
一個進程向管道中寫的內容被管道另外一端的進程讀出。
寫入的內容每次都添加在管道緩衝區的末尾,
而且每次都是從緩衝區的頭部讀出數據。
 
1、建立匿名管道
  #include <unistd.h>
1.int pipe(int fd[2]); fd[0] 讀通道 fd[1]寫通道  調用成功返回0不然返回-1
特色:1.沒有名字,爲了一次使用創建的
      2.兩個文件描述符同時打開,向一個沒有寫進程的向管道寫數據的管道讀數據read 返回文件結束,向一個沒有讀進程讀數據的管道寫數據,返回錯誤
      3.不容許文件定位,讀寫都是順序的

else if(pid>0)
{
  close(fd[0]);
  write(fd[1],"How are you?\n",12);//對管道進行讀寫操做時,先關閉另一端
}
else
{
  close(fd[1]);
  n=read(fd[0],buffer,1024);
  write(STDOUT_FILENO,buffer,n);

 }

2.if(dup2(fd[0],STDIN_FILENO)!=STDIN_FILENO)//標準輸入設爲管道的讀數據一端
{
  printf("dup2 error to stdin!\n");
  exit(1);
}
close(fd[0]);

 

 

3.FILE* popen(char *comd,char * type)調用fork 和exec函數執行命令行命令

  if(popen("upcase","w"))==null)

{

  printf("popen error");

exit(1);

}

 pcloese(FILE * fp)

if(pclose(fp)==-1)

{

 printf("pclose error.\n");

 exit(1);

}

二.命名管道

不一樣之處:1)可用於非相同祖先進程之間通訊

             2)內容存儲在文件系統中,除非刪除,不然不會消失

操做:1)必須打開才能進行讀寫操做

         2)沒有其餘進程打開一個命名管道式,就對其進行讀操做,會產生SIGPIPE信號;全部寫進程都關閉了,讀管道認爲到達文件末尾

         3)多個進程寫操做進行的狀況下,寫交錯的狀況會發生。

應用:shell命令行傳遞命令

        客戶服務器之間交換數據 

int  mkfifo(char * pathname,mode_t mode)

int mknod(chat *pathname,mode_t mode,dev_t dev);

服務器程序

讀取數據

/**ex6-12client.c**/
#include<stdio.h>
#include<stdlib.h>

#define FIFO_FILE "MYFIFO"

int main(int argc,char *argv[])
{
FILE *fp;
int i;
if(argc<=1)
{
printf("usage:%s<pathname>\n",argv[0]);
exit(1);
}
if((fp=fopen(FIFO_FILE,"w"))==NULL)
{
printf("open fifo failed.\n");
exit(1);
}
for(i=1;i<argc;i++)
{
if(fputs(argv[i],fp)==EOF)
{
printf("write fifo error.\n");
exit(1);
}
if(fputs("",fp)==EOF)
{
printf("write fifo error.\n");
exit(1);
}
}
fclose(fp);
return 0;
}

 

 

 

客戶端程序,輸入數據

/**ex6-12client.c**/
#include<stdio.h>
#include<stdlib.h>

#define FIFO_FILE "MYFIFO"

int main(int argc,char *argv[])
{
FILE *fp;
int i;
if(argc<=1)
{
printf("usage:%s<pathname>\n",argv[0]);
exit(1);
}
if((fp=fopen(FIFO_FILE,"w"))==NULL)
{
printf("open fifo failed.\n");
exit(1);
}
for(i=1;i<argc;i++)
{
if(fputs(argv[i],fp)==EOF)
{
printf("write fifo error.\n");
exit(1);
}
if(fputs("",fp)==EOF)
{
printf("write fifo error.\n");
exit(1);
}
}
fclose(fp);
return 0;
}

 

三。system V IPC 機制

1.關鍵字和標識符

標識符:每一個對象都和惟一一個引用標識符相連,惟一性侷限在同一IPC對象內

關鍵字:用於定位system v中IPC機制的對象的引用標識符,類型爲key_t 包含在頭文件<sys/types.h>中

#include<sys/ipc.h>

key_t ftok(const char *path,int id) 返回key_t    pathname 路徑名 id只有低8位有效

2.ipc_perm   包含於頭文件<sys/ipc.h>中包含了uid,gid,cuid,cgid,mod,seq(應用序號),key關鍵字

3.ipcs 用於顯示IPC機制全部對象的狀態

 

一).消息隊列

每一個消息隊列都有一個msqid_ds結構與其關聯:

struct msqid_ds
  {
     struct msqid_ds {
    struct ipc_perm msg_perm;  /*隊列對應的ipc_perm指針
    struct msg *msg_first;      /* first message on queue,unused  */
    struct msg *msg_last;       /* last message in queue,unused */
    __kernel_time_t msg_stime;  /* last msgsnd time */
    __kernel_time_t msg_rtime;  /* last msgrcv time */
    __kernel_time_t msg_ctime;  /* last change time */
    unsigned long  msg_lcbytes; /* Reuse junk fields for 32 bit */
    unsigned long  msg_lqbytes; /* ditto */
    unsigned short msg_cbytes;  /* current number of bytes on queue */
    unsigned short msg_qnum;    /* number of messages in queue */
    unsigned short msg_qbytes;  /* max number of bytes on queue */
    __kernel_ipc_pid_t msg_lspid;   /* pid of last msgsnd */
    __kernel_ipc_pid_t msg_lrpid;   /* last receive pid */
};

1.建立消息隊列

#include<sys/types.h>

#include<sys/ipe.h>

#include<sys/msq.h>

int msgget(key_t key,int flag);

1)key==IPC_PRIVATE  flag 爲什麼值都建立消息隊列

2)key!=IPV_PRIVATE flag 置位IPC_CREATE 沒設置 IPC_EXCL 則既能打開又能建立;key 與內核存在關鍵字相同時,打開消息隊列,返回引用標誌符;不然執行建立,返回標識符

3))key!=IPV_PRIVATE flag 置位IPC_CREATE 置 IPC_EXCL 執行建立,若存在消息隊列,則返回錯誤

flag 設置權限

例如:

if((msqid=msgget(key,IPC_CREAT|0660))==-1)
{
  printf("the message failed.\n");
  exit(1);
}
printf("the message succed :msqid=%d\n",msqid);

2.發送接收消息

1)消息結構

struct mymsg

{

 long mtype;

 char mtext[512];

}

發送

#include<sys/types.h>

#include<sys/ipc.h>

#include<sys/msg.h>

int msgsnd(int msqid,const void *ptr,size_t nbytes,int flags);

msqid:消息隊列的引用標識符,插入隊尾

ptr :指向長整數的指針

nbytes:消息的長度,字節

flag:0或者IPC_NOWAIT   

函數成功返回0不然返回-1

int msgrcv(int msqid,const void *ptr,size_t nbytes,long type,int flags); 

type=0 f返回消息隊列中第一個消息;〉返回類型域等於這個值得消息;<0 在類型域<=type絕對值的消息中,類型域最小的第一個消息

flag: IPC——NOWAIT當type 無效直接返回錯誤,不然阻塞發送消息進程,直至type變爲有效

        MSG_NOERROR,當數據長度超過nbytes時,截斷數據,正確返回,不然函數錯誤返回,消息仍然存在隊列中

調用成功返回字節數,不然返回-1

接收消息

/**ex6-14**/
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
struct my_msg
{
long int my_msg_type;
char text[BUFSIZ];
}msgbuf;

int main()
{
int running=1;
int msgid;
msgid=msgget((key_t)1234,0666|IPC_CREAT);
if(msgid==-1)
{
printf("msgget failed!\n");
exit(1);
}
while(running)
{
printf("Enter somt text:");
fgets(msgbuf.text,BUFSIZ,stdin);
msgbuf.my_msg_type=1;
if(msgsnd(msgid,(void *) &msgbuf,BUFSIZ,0)==-1)
{
printf("msgsnd failed!\n");
exit(1);
}

if(strncmp(msgbuf.text,"end",3)==0)
running=0;

}

//接收信息

int main()
{
int running=1;
int msgid;
long int msg_to_receive=0;
msgid=msgget((key_t)1234,0666|IPC_CREAT);
if(msgid==-1)
{
printf("msgget failed!\n");
exit(1);
}
while(running)
{
if(msgrcv(msgid,(void *)& msgbuf,BUFSIZ,msg_to_receive,0)==-1)
{
printf("received failed\n");
exit(1);

}
printf("You wrote:%s ",msgbuf.text);
if(strncmp(msgbuf.text,"end",3)==0)
running=0;

}

if(msgctl(msgid,IPC_RMID,0)==-1)
{
printf("msgct(IPC_RMID) failed!\n");
exit(1);

}
return 0;
}

 

3.控制消息

int msgctl(int msqid,int cmd,struct msqid_ds *buf)成功返回0 不然返回-1

cmd:IPC_STAT 複製數據到buf

      IPC_SET 利用buf設置msqid

      IPC_RMID 刪除msqid 及數據結構

 

二)信號量

共享資源:互斥共享:同一時刻只容許一個進程對資源進行操做

             同步共享:同一時刻若干資源進行操做

1.信號量結構體
內核爲每一個信號量集維護一個信號量結構體,可在<sys/sem.h>找到該定義:
struct semid_ds {
struct ipc_perm sem_perm; /* 信號量集的操做許可權限 */
struct sem *sem_base; /* 某個信號量sem結構數組的指針,當前信號量集
中的每一個信號量對應其中一個數組元素 */
ushort sem_nsems; /* sem_base 數組的個數 */
time_t sem_otime; /* 最後一次成功修改信號量數組的時間 */
time_t sem_ctime; /* 成功建立時間 */
};

 

 

struct sem {
ushort semval; /* 信號量的當前值 */
short sempid; /* 最後一次返回該信號量的進程ID 號 */
ushort semncnt; /* 等待semval大於當前值的進程個數 */
ushort semzcnt; /* 等待semval變成0的進程個數 */
};

 

2.建立打開信號量

int semget(key_t key, int nsems, int oflag)出錯返回-1

if((sid=semget(keyval,numsems,IPC_CREAT|0660))==-1)
return -1;

(1) nsems>0 : 建立一個信的信號量集,指定集合中信號量的數量,一旦建立就不能更改。
(2) nsems==0 : 訪問一個已存在的集合
(3) 返回的是一個稱爲信號量標識符的整數,semop和semctl函數將使用它。
(4) 建立成功後信號量結構被設置:
.sem_perm 的uid和gid成員被設置成的調用進程的有效用戶ID和有效組ID
.oflag 參數中的讀寫權限位存入sem_perm.mode
.sem_otime 被置爲0,sem_ctime被設置爲當前時間
.sem_nsems 被置爲nsems參數的值
該集合中的每一個信號量不初始化,這些結構是在semctl,用參數SET_VAL,SETALL
初始化的。
semget函數執行成功後,就產生了一個由內核維持的類型爲semid_ds結構體的信號量
集,返回semid就是指向該信號量集的引索。

2.操做

int semop(int semid, struct sembuf *opsptr, size_t nops);
(1) semid: 是semget返回的semid
(2)opsptr: 指向信號量操做結構數組
(3) nops : opsptr所指向的數組中的sembuf結構體的個數
struct sembuf {
short sem_num; // 要操做的信號量在信號量集裏的編號,
short sem_op; // 信號量操做
short sem_flg; // 操做表示符
};

3.控制

int semctl(int semid,int semnum,int cmd,[union semun arg]);

semctl(sem_id,0,SETVAL,1);

semid:信號量集合合法標識

semnum:信號量集合的特定信號量的編號

cmd 命令

arg 可選

特殊命令返回特殊值,其餘命令充公返回0,失敗返回-1

 

三)共享內存

#include<sys/types.h>

#include<sys/ipc.h>

#include<sys/shm.h>

  1. int shmget(key_t key, size_t size, int shmflg);成功返回值爲共享內存的引用標識,失敗返回-1
參數:
key : 和信號量同樣,程序須要提供一個參數key,
      它有效地爲共享內存段命名。
      
      有一個特殊的鍵值IPC_PRIVATE, 
      它用於建立一個只屬於建立進程的共享內存,
      一般不會用到。
size: 以字節爲單位指定須要共享的內存容量。
shmflag: 包含9個比特的權限標誌,
         它們的做用與建立文件時使用的mode標誌是同樣。
         由IPC_CREAT定義的一個特殊比特必須和權限標誌按位或
         才能建立一個新的共享內存段。
第一次建立共享內存段時,它不能被任何進程訪問。
要想啓動對該內存的訪問,
必須將其鏈接到一個進程的地址空間
這個工做由shmat函數完成:
  1. void *shmat(int shm_id, const void *shm_addr, int shmflg);
參數:
shm_id : 由shmget返回的共享內存標識。
shm_add: 指定共享內存鏈接到當前進程中的地址位置。
         它一般是一個空指針, 
         表示讓系統來選擇共享內存出現的地址。
shmflg : 是一組標誌。
         它的兩個可能取值是:
         SHM_RND, 和shm_add聯合使用,
                  用來控制共享內存鏈接的地址。
         SHM_RDONLY, 它使鏈接的內存只讀
         
返回值:
若是調用成功, 返回一個指向共享內存第一個字節的指針;
若是失敗,返回-1.
 
共享內存的讀寫權限由它的屬主(共享內存的建立者),
它的訪問權限和當前進程的屬主決定。
共享內存的訪問權限相似於文件的訪問權限。
 
3. shmdt
將共享內存從當前進程中分離
  1. int shmdt(const void *shm_addr);
shm_addr: shmat返回的地址指針。
 
成功時,返回0,
失敗時,返回-1.
 
NOTE:
共享內存分離並未刪除它,
只是使得該共享內存對當前進程再也不可用。
 
4. shmctl
共享內存的控制函數
  1. int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
shmid_ds結構至少包含如下成員:
  1. struct shmid_ds {
  2.   uid_t shm_perm.uid;
  3.   uid_t shm_perm.gid;
  4.   mode_t shm_perm.mode;
  5. }
 
參數:
shm_id : 是shmget返回的共享內存標識符。
command: 是要採起的動做,
         它能夠取3個值:
 
IPC_STAT  把shmid_ds結構中的數據設置爲共享內存的當前關聯值
IPC_SET   若是進程有足夠的權限,
          就把共享內存的當前關聯值設置爲shmid_ds結構中給出的值
IPC_RMID  刪除共享內存段
 
buf    : 是一個指針,
         包含共享內存模式和訪問權限的結構。
 
返回值:
成功時,返回0,
失敗時,返回-1.
相關文章
相關標籤/搜索