Linux系统进程间通信编程

  进程控制原语,并且观察了如何调用多个进程。但是这些进程之间交换信息的唯一途径就是传送打开文件,经由fork或exec来传送,也可以通过文件系统来传送。本文将说明进程之间相互通信的其他技术——进程间通信(InterProcess Communication,IPC).IPC的方式通常有管道(无名管道和命名管道)、消息队列、共享内存、信号、信号量、Socket、stream(前五个为单机,后两个为多机).

   1.管道

    管道通常也称无名管道,是最古老的通信方式。

    特点:1.它是半双工的,具有固定的读端和写段。2.它只能用于具有亲缘关系的进程之间的通信(也就是父子进程或者兄弟进程)。3.它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。

   fd[0]为读端,fd[1]为写端。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>




int main()
{
     int pid;
     int fd[2]; 
     char readBuf[1024] = {0};
//   int pipe(int pipefd[2]);
     if(pipe(fd) == -1)
     {
          printf("create pipe failed\n");
     }
     
     pid = fork();
   
     if( pid < 0 )
     {
        printf("create fork failed\n");
     }
     else if(pid > 0)
     {
        sleep(3);
        printf("there is father\n");
        close(fd[0]);//关闭读端,只能写
        write(fd[1],"yi shu wei ban",strlen("yi shu wei ban"));
        wait();
     }
     else if(pid == 0)
     {
        printf("there is child\n");
        close(fd[1]);//关闭写端,只能读
        read(fd[0],readBuf,1024);
        printf("read from father : %s\n",readBuf);
        exit(0);
     }
     return 0; 
}

   2.命名管道

      管道具有两种局限性:1.它们是半双工的。2.管道只能在具有公共祖先的两个进程之间使用。通常,一个管道由一个进程创建,在进程调用fork之后,这个管道就只能在父子进程间使用。

       命名管道FIFO没有第二种局限性。

       创建管道并打开管道

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>


int main()
{
//      int mkfifo(const char *pathname, mode_t mode);
        if(mkfifo("./file1",0600) == -1 && errno != EEXIST)
         {
            printf("creat filed\n");
            perror("why");
         }
            
         open("./file1",O_RDONLY);
         printf("open success\n");


   return 0;     
}

    当 open 一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:

  • 若没有指定O_NONBLOCK(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO。类似的,只写 open 要阻塞到某个其他进程为读而打开它。

  • 若指定了O_NONBLOCK,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其errno置ENXIO。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>


int main()
{ 
        
//      int mkfifo(const char *pathname, mode_t mode);
            
         open("./file1",O_WRONLY);
         printf("write open success\n");
         

   return 0;     
}

 通过管道传递信息:

   read.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>


int main()
{
        int n_read = 0;
        int fd;
//      int mkfifo(const char *pathname, mode_t mode);
        char readBuf[30] = {0};    
        fd = open("./file1",O_RDONLY);
        while(1)
        {
             n_read = read(fd,readBuf,30);
         
             printf("read %d byte from fifo,context : %s\n",n_read,readBuf);
        }
        close(fd);
        return 0;     
}

   write.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

int main()
{ 
        int fd;
        char *str = "meesage from fifo";
//      int mkfifo(const char *pathname, mode_t mode);
            
         fd = open("./file1",O_WRONLY);
         printf("write open success\n");
         while(1)
         {
                 sleep(1);
        	 write(fd,str,strlen(str));
         }
         close(fd);
   return 0;     
}

  3.消息队列

     消息队列:是消息的链接表,存放在内核。一个消息队列由一个标识符(即队列ID)来标识。

     特点:1.消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。

                2.消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。

                3.消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

   

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

struct msgbuf
{
    long mtype;//类型
    char mtext[128];//内容
};



int main()
{
     struct msgbuf readbuf;
     struct msgbuf writebuf = {988,"thank you"};
     int msqid;
     key_t key;

     key = ftok(".",1);
     printf("key = %x\n",key);
//   int msgget(key_t key, int msgflg);
     if(msqid = msgget(key,IPC_CREAT|0777) == -1)
      {
         printf("failed\n");
         perror("why");
      }
//   ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
     msgrcv(msqid,&readbuf,sizeof(readbuf.mtext),888,0);
     printf("read from que:%s\n",readbuf.mtext);  
     
     msgsnd(msqid,&writebuf,strlen(writebuf.mtext),0);

//   int msgctl(int msqid, int cmd, struct msqid_ds *buf);
     msgctl(msqid,IPC_RMID,NULL);

     return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

struct msgbuf
{
    long mtype;
    char mtext[128];
};



int main()
{
     struct msgbuf writebuf = {888,"this is message from que"};
     struct msgbuf readbuf;
     int msqid;
     key_t key;
   
     key = ftok(".",1);
     printf("key = %x\n",key);
//   int msgget(key_t key, int msgflg);
    if(msqid = msgget(key,IPC_CREAT|0777) == -1)
     {
         printf("failed\n");
         perror("why");
     }
//   int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
     msgsnd(msqid,&writebuf,strlen(writebuf.mtext),0);      

     msgrcv(msqid,&readbuf,sizeof(readbuf.mtext),988,0);
     printf("return form get :%s\n",readbuf.mtext);
     
     msgctl(msqid, IPC_RMID,NULL);//在内核中删除队列
     

     return 0;
}

  4.共享内存

     指两个或多个进程共享一个给定的存储区。(ipcs -m查看)

     特点:1.共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。

              2.因为多个进程可以同时操作,所以需要进行同步。

              3.信号量(同步)+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

     写

#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
      char *shmaddr;
      int shmid;
      key_t key;
      key = ftok(".",1);
//    int shmget(key_t key, size_t size, int shmflg);
      shmid = shmget(key,1024*4,IPC_CREAT|0666);//钥匙介质,大小,方式
      if(shmid == -1)
       {
            printf("failed\n");
            perror("why");
            exit(-1);
       }
       exit(0);
//     void *shmat(int shmid, const void *shmaddr, int shmflg);
       shmaddr = shmat(shmid,0,0);//id,自动配置内存,模式(默认读写)
       printf("shmat ok\n");
      
       strcpy(shmaddr,"yi shu wei ban");

       sleep(5);
//     int shmdt(const void *shmaddr);
       shmdt(shmaddr);
//     int shmctl(int shmid, int cmd, struct shmid_ds *buf);
       shmctl(shmid,IPC_RMID,0); 
       printf("quit\n");
       return 0;

}

    读

#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
      char *shmaddr;
      int shmid;
      key_t key;
      key = ftok(".",1);
//    int shmget(key_t key, size_t size, int shmflg);
      shmid = shmget(key,1024*4,0);//获取不创建
      if(shmid == -1)
       {
            printf("failed\n");
            perror("why");
            exit(-1);
       }
//     void *shmat(int shmid, const void *shmaddr, int shmflg);
       shmaddr = shmat(shmid,0,0);
       printf("shmat ok\n");
       printf("data is : %s\n",shmaddr);

       sleep(5);
//     int shmdt(const void *shmaddr);
       shmdt(shmaddr);
//     int shmctl(int shmid, int cmd, struct shmid_ds *buf);

       printf("quit\n");
       return 0;

}

  5.信号

     对于Linux来说,实际信号是软中断,许多重要的程序都需要处理信号。信号,为Linux提供了处理异步事件的方法。比如,终端用户输入了ctrl+c来中断程序,会通过信号机制停止一个程序。

     每个信号都有一个名字和编号,可以使用(kill -l)查看信号的名字以及序号。信号的处理有三种方法,分别是:忽略、捕捉和默认动作。

  • 忽略信号,大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(SIGKILLSIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景
  • 捕捉信号,需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
  • 系统默认动作,对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。

     信号注册函数:

#include <stdio.h>
#include <signal.h>
  
// typedef void (*sighandler_t)(int);

// sighandler_t signal(int signum, sighandler_t handler);

void handler(int signum)
{
    printf("get signum = %d\n",signum);
    switch(signum) 
    {
         case 2:
               printf("SIGINT\n");
               break;
         case 9:
               printf("SIGKILL\n");
               break;
         case 10:
               printf("SIGUSR1\n");
               break;
    }
}

int main()
{
   signal(SIGINT,SIG_IGN);
   signal(SIGKILL,SIG_IGN);
   signal(SIGUSR1,handler);//信号捕捉
   while(1);	
   return 0;
}

    信号发送函数:

#include <stdio.h>
#include <sys/types.h>
#include <signal.h>


int main(int argc,char **argv)
{
    int pid;
    int signum;
    char cmd[128] = {0};
    signum = atoi(argv[1]);//转换
    pid = atoi(argv[2]);//转换
   
    printf("num = %d,pid = %d\n",signum,pid);
//  int kill(pid_t pid, int sig);
    kill(pid,signum);//kill指令发送信号

   // sprintf(cmd,"kill -%d %d\n",signum,pid);  //构造字符串  
   // system(cmd);

    printf("kill ok\n");
       
    return 0;
}

   高级信号(在发送信号的同时还可以携带信息)

    信号注册函数:

 关于sigaction函数做下注释:1.信号号码或名字2.结构体3.备份

                          (1).结构体中(*sa_handler)(int)与简单信号的handler相似,不传递其他额外数据。

                          (2)信号处理函数,接受额外数据。括号里又包含3个类型:1.num2.结构体siginfo_t3.指针:有内容非空,无内容为空。

                          (3)阻塞信号集(默认阻塞)

                          (4)表示能够接受数据。                

 结构体siginfo_t:

  处理

#include <stdio.h>
#include <signal.h>

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

void handler(int signum,siginfo_t *info, void *context)
{
     printf("get signum %d\n",signum);
     if(context != NULL)
      {
         printf("get data = %d\n",info->si_int);
         printf("get data = %d\n",info->si_value.sival_int);
         printf("get pid  = %d\n",info->si_pid);
      }
     
}

int main()
{
   struct sigaction act;
   printf("pid is %d\n",getpid());
   
   act.sa_sigaction = handler;
   act.sa_flags = SA_SIGINFO;   

   sigaction(SIGUSR1,&act,NULL);
   while(1);
   return 0;
}

  发送

#include <stdio.h>
#include <signal.h>


int main(int argc,char **argv)
{

    union sigval value;
    int pid;
    int signum;
    signum = atoi(argv[1]);
    pid    = atoi(argv[2]);

    value.sival_int = 100;
   
//  int sigqueue(pid_t pid, int sig, const union sigval value);
    sigqueue(pid,signum,value);
    printf("pid is %d\n",getpid());
    printf("done\n");
    
    return 0;
}

   6.信号量

     信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>

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

union semun 
{
               int              val;    /* Value for SETVAL */
               struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
               unsigned short  *array;  /* Array for GETALL, SETALL */
               struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                           (Linux-specific) */
};

//  若信号量值为1,获取资源并将信号量值-1 
//  若信号量值为0,进程挂起等待
void PGetKey(int id)//P操作
{
    struct sembuf set;
    set.sem_num = 0;
    set.sem_op  = -1;
    set.sem_flg = SEM_UNDO;
    semop(id,&set,1);
                  //个数	
}


//  释放资源并将信号量值+1
//  如果有进程正在挂起等待,则唤醒它们
void VGet_back_Key(int id)//V操作
{
    struct sembuf set;
    set.sem_num = 0;
    set.sem_op  = 1;
    set.sem_flg = SEM_UNDO;
    semop(id,&set,1);
}

int main()
{
      int pid;
      int semid;
      union semun initsem;
      key_t key;
      key = ftok(".",2);
//    int semget(key_t key, int nsems, int semflg);//第二个参数:信号量集当中信号量的个数
      semid = semget(key,1,IPC_CREAT|0666);
      initsem.val = 0;
//    int semctl(int semid, int semnum, int cmd, ...);
                   //操作第0个信号量
      semctl(semid,0,SETVAL,initsem);//初始化信号量
                      //setval设置信号量的值
      pid = fork();
      
      if(pid >0)
      { 
         PGetKey(semid);
         printf("this is father\n");
         VGet_back_Key(semid);
         semctl(semid,0, IPC_RMID);
      }
      else if(pid == 0)
      {
         printf("this is child\n");
         VGet_back_Key(semid);
      }
      else
      {
         printf("error\n");
      }

      return 0;  
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值