嵌入式学习之系统编程(八)IPC、管道(有名与无名)和信号通信(6.3)

发布于:2025-06-10 ⋅ 阅读:(23) ⋅ 点赞:(0)

目录

一、IPC进程间通信

三大类

二、管道

1、管道分类:

2、用处:

3、管道的特性:

三、管道的使用

1、使用关键

2、使用步骤

四、无名管道pipe

1、 流程:

(1)创建并打开管道: pipe函数

(2)无名管道的读写:文件IO的读写方式

(3)关闭管道: close()函数

2、代码示例

3、练习:文件复制(无名管道)

4、补充问题

五、有名管道fifo

1、使用步骤

2、相关函数

(1)创建有名管道:mkfifo函数

(2)打开有名管道 :open函数

(3)管道的读写: 文件IO

(4)关闭管道

(5)卸载管道:remove()函数

3、代码示例

(1)fifo_w.c

(2)fifo_r.c

4、练习:文件复制(有名管道)

(1)fifo_w.c

(2)fifo_r.c

5、补充问题

六、信号通信

1、如何响应:

2、信号的含义

3、发送端(发信号)

(1)命令

(2)kill函数

(3)代码示例(kill)

(4)raise函数(与kill相似)

(5)alarm函数

(6)代码示例(alarm)

(7)pause函数

(8)代码示例(pause)

4、接收端

(1)signal函数(信号注册函数)(回调函数)

(2)代码示例

七、扩展

1、SIGCHLD

2、信号处理函数中的num,代表的是该信号的数字

一、IPC进程间通信

IPC   进程间通信  interprocess communicate

三大类

(1)古老的通信方式:无名管道  有名管道  信号;

(2)IPC对象通信 system v(unix操作系统)    BSD     suse fedora   kernel.org

消息队列(用的相对少,这里不讨论)(与管道用法相似)
共享内存(给两个进程共享内存,相当于在内核中调malloc)
信号量集 (进程这里做互斥与同步用信号量)

(3)socket通信

网络通信(不同主机之间交互)

线程信号,posix  sem_init

特例:古老的通信方式中信号是唯一的异步通信

           所有的通信方式中共享内存是唯一的最高效

二、管道

1、管道分类:

无名管道、有名管道

2、用处:

无名管道 ===》pipe ==》只能给有亲缘关系进程通信
有名管道 ===》fifo ==》可以给任意单机进程通信

3、管道的特性:

(1)管道是 半双工的工作模式
(2)所有的管道都是特殊的文件不支持定位操作。不支持lseek->> fd  fseek ->>FILE* 
(3)管道是特殊文件,读写使用文件IO。

其中具有缓冲区,可以考虑,如果是字符串的话,使用fgets,fread,fgetc,
最好使用:open,read,write,close;

三、管道的使用

1、使用关键

(1)读端存在,一直向管道中去写,超过64k,写会阻塞(一般取4k解除阻塞);


(2)写端是存在的,读管道,如果管道为空的话,读会阻塞;


(3)管道破裂(出现错误),读端关闭,写管道;

set follow-fork-mode parent

使用gdb调试时,敲上面的命令,进入子进程,一般默认父进程

(4) read 0 :写端关闭,如果管道没有内容,read 0 ;

2、使用步骤

创建管道 ==》读写管道 ==》关闭管道

四、无名管道pipe

无名管道 ===》管道的特例 ===>pipe函数
特性:
(1)亲缘关系进程使用
(2)有固定的读写端

1、 流程:

(1)创建并打开管道: pipe函数

头文件:#include <unistd.h>
函数原型:int pipe(int pipefd[2]);
功能:创建并打开一个无名管道
参数:pipefd[0] ==>无名管道的固定读端
  pipefd[1] ==>无名管道的固定写端
返回值:成功 0
               失败 -1;

注意事项:无名管道的架设应该在fork之前进行。

(2)无名管道的读写:文件IO的读写方式

读: read()
写: write()

(3)关闭管道: close()函数

2、代码示例

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


int    main(int argc, char **argv)
{
    int fd[2]={0};
    int ret = pipe(fd);
    if(ret == -1)
    {
          perror("pipe");
        return 1;
    }
    pid_t pid = fork();
    if(pid>0)
    {
        // fd[0] read   fd[1] write
          close(fd[0]);  //close read end 
          sleep(3);
          write(fd[1],"hello",5);
          close(fd[1]);
    }

    else if (0 == pid)
    {
        close(fd[1]);
        char buf[10]={0};
        read(fd[0],buf,sizeof(buf));
        printf("father:%s\n",buf);
        close(fd[0]);


    }
    else  
    {
        perror("fork");
        return 1;
    }


    system("pause");
    return 0;
}

3、练习:文件复制(无名管道)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <fcntl.h>

int    main(int argc, char **argv)
{
    int fd[2]={0};
    int ret = pipe(fd);
    if(ret == -1)
    {
          perror("pipe");
        return 1;
    }
    pid_t pid = fork();
    if(pid>0)
    {
        // fd[0] read   fd[1] write
          close(fd[0]);  //close read end 
        
            int srcfd = open("/home/linux/1.png",O_RDONLY);
            if(-1 == srcfd)
            {
                perror("open");
                exit(1);
            }

            while(1)
            {
                char buf[4096]={0};
                int ret = read(srcfd,buf,sizeof(buf));
                if(0 == ret)
                {
                    break;
                }
                write(fd[1],buf,ret); //sizeof(buf) strlen(buf) 
            }
          close(fd[1]);
          close(srcfd);
    }

    else if (0 == pid)
    {
        close(fd[1]);
       
        int dstfd = open("2.png",O_WRONLY|O_CREAT|O_TRUNC,0666);
        if(-1 == dstfd)
        {
            perror(" child open");
            return 1;
        }
        while(1)
        {
            char buf[4096]={0};
            int ret = read(fd[0],buf,sizeof(buf));
            if(0 == ret)
            {
                break;
            }
            write(dstfd,buf,ret);
        }
        close(fd[0]);
        close(dstfd);

    }
    else  
    {
        perror("fork");
        return 1;
    }


   // system("pause");
    return 0;
}

4、补充问题

(1)父子进程是否都有fd[0] fd[1],
   如果在单一进程中写fd[1]能否直接从fd[0]中读到。

   可以,写fd[1]可以从fd[0]读

(2)管道的数据存储方式是什么样的
   数据是否一直保留?
    栈, 先进后出
   队列形式存储 读数据会剪切取走数据不会保留
   先进先出

(3)管道的数据容量是多少,有没有上限值。
    操作系统的建议值: 512* 8 = 4k
    代码测试实际值:   65536byte= 64k

(4)管道的同步效果如何验证?读写同步验证。
    读端关闭能不能写? 不可以 ===>SIGPIPE 异常终止 
    写端关闭能不能读? 可以,取决于pipe有没有内容,===>read返回值为0 不阻塞

    结论:读写端必须同时存在,才能进行管道的读写。
(5)固定的读写端是否就不能互换?
    能否写fd[0] 能否读fd[1]?   不可以,是固定读写端。

五、有名管道fifo

有名管道===》fifo ==》有文件名称的管道
  文件系统中可见

适用场景:本机上的(不同)任意进程间通信

1、使用步骤

创建有名管道 ==》打开有名管道 ==》读写管道==》关闭管道  ==》卸载有名管道

2、相关函数

(1)创建有名管道:mkfifo函数

头文件:#include <sys/types.h>
              #include <sys/stat.h>
函数原型:int mkfifo(const char *pathname, mode_t mode);
功能:在指定的pathname路径+名称下创建一个权限为mode的有名管道文件。
参数:pathname要创建的有名管道路径+名称
           mode  8进制文件权限。
返回值:成功 0
              失败  -1;

(2)打开有名管道 :open函数

注意:该函数使用的时候要注意打开方式,
因为管道是半双工模式,所有打开方式直接决定
当前进程的读写方式。
一般只有如下方式:
int fd-read = open("./fifo",O_RDONLY); ==>fd 是固定读端
int fd-write = open("./fifo",O_WRONLY); ==>fd 是固定写端
可以以 O_RDWR 方式打开文件。
不能有 O_CREAT 选项,因为创建管道有指定的mkfifo函数

(3)管道的读写: 文件IO

读: read(fd-read,buff,sizeof(buff));
写: write(fd-write,buff,sizeof(buff));

(4)关闭管道

close(fd);

(5)卸载管道:remove()函数

函数原型:int unlink(const char *pathname);
功能:将指定的pathname管道文件卸载,同时从文件系统中删除。
参数: ptahtname 要卸载的有名管道 
返回值:成功 0
               失败  -1;

3、代码示例

(1)fifo_w.c

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char **argv)
{
  int ret = mkfifo("fifo", 0666);
  if (-1 == ret)
    {
      if (EEXIST != errno)
        {
          perror("mkfifo");
          return 1;
        }
    }

  int fd = open("fifo", O_WRONLY);
  if (-1 == fd)
    {
      perror("open fifo");
      return 1;
    }

  write(fd, "hello", 5);
  close(fd);
  // system("pause");
  return 0;
}

(2)fifo_r.c

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char **argv)
{
  int ret = mkfifo("fifo", 0666);
  if (-1 == ret)
    {
      if (EEXIST != errno)
        {
          perror("mkfifo");
          return 1;
        }
    }

  int fd = open("fifo", O_RDONLY);
  if (-1 == fd)
    {
      perror("open fifo");
      return 1;
    }
  char buf[10] = {0};
  read(fd, buf, sizeof(buf));
  printf("w:%s\n", buf);
  close(fd);
  remove("fifo");
  // system("pause");
  return 0;
}

4、练习:文件复制(有名管道)

(1)fifo_w.c

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char **argv)
{
  int ret = mkfifo("fifo", 0666);
  if (-1 == ret)
    {
      if (EEXIST != errno)
        {
          perror("mkfifo");
          return 1;
        }
    }

  int fd = open("fifo", O_WRONLY);
  if (-1 == fd)
    {
      perror("open fifo");
      return 1;
    }

  int srcfd= open("/home/linux/1.png",O_RDONLY);
  if(-1 == srcfd)
  {
     perror("open 1.png");
      return 1;
  }
  while(1)
  {
    char buf[4096]={0};
    int ret = read(srcfd,buf,sizeof(buf));
    if(0 == ret)
    {
      break;
    }
    write(fd,buf,ret);
  }
  close(fd);
  close(srcfd);
  // system("pause");
  return 0;
}

(2)fifo_r.c

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char **argv)
{
  int ret = mkfifo("fifo", 0666);
  if (-1 == ret)
    {
      if (EEXIST != errno)
        {
          perror("mkfifo");
          return 1;
        }
    }

  int fd = open("fifo", O_RDONLY);
  if (-1 == fd)
    {
      perror("open fifo");
      return 1;
    }
  char buf[10] = {0};
  read(fd, buf, sizeof(buf));
  printf("w:%s\n", buf);
  close(fd);
  //remove("fifo");
  // system("pause");
  return 0;
}

5、补充问题

有名管道 

    (1)是否需要同步,以及同步的位置。
        读端关闭 是否可以写,不能写什么原因。
        写端关闭 是否可以读。

        结论:有名管道执行过程过必须有读写端同时存在。
              如果有一端没有打开,则默认在open函数部分阻塞。

    (2)有名管道是否能在fork之后的亲缘关系进程中使用。
        结论: 可以在有亲缘关系的进程间使用。
        注意: 启动的次序可能会导致其中一个稍有阻塞。

    (3)能否手工操作有名管道实现数据的传送。
        读: cat  fifoname
        写: echo "asdfasdf" > fifoname

六、信号通信

应用:异步通信。 中断..
    1~64;32应用编程。

1、如何响应:

收到信号默认采取以下动作:

//关闭(结束进程)

 Term   Default action is to terminate the process.

//忽略

       Ign    Default action is to ignore the signal.
       wait
// 关闭,并保存关键点信息      

       Core   Default action is to  terminate  the  process  and  dump  core  (see
              core(5)).
        gdb a.out -c core

//暂停
       Stop   Default action is to stop the process.

//继续

       Cont   Default  action  is  to  continue  the  process  if  it is currently  stopped.

2、信号的含义

信号  kill  -l 命令 ==>前32个有具体含义的信号

3、发送端(发信号)

(1)命令

kill      -xx     xxxx
发送进程  信号    接收进程
    kill -9 1000
    a.out  9 1000   

(2)kill函数(发送函数)

头文件:#include <sys/types.h>
              #include <signal.h>
函数原型:int kill(pid_t pid, int sig);
功能:通过该函数可以给pid进程发送信号为sig的系统信号。
参数:pid 要接收信号的进程pid
          sig 当前程序要发送的信号编号 《=== kill  -l
返回值:成功 0
               失败  -1;

(3)代码示例(kill)

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/types.h>
#include <unistd.h>
// ./a.out 1000 9
int main(int argc, char **argv)
{
    if(argc<3)
    {
        printf("usage: ./a.out pid signum\n");
        return 1;
    }
    pid_t pid = atoi(argv[1]);
    int num  = atoi(argv[2]);
    int ret = kill(pid,num);
    if(-1 == ret)
    {
        perror("kill");
        return 1;
    }
  system("pause");
  return 0;
}

(4)raise函数(与kill相似)

int raise(int sig)== kill(getpid(),int sig);
功能:给进程自己发送sig信号

(5)alarm函数

函数原型:unsigned int alarm(unsigned int seconds);

发送的信号:SIGALAM
功能:定时由系统给当前进程发送信号,也称为闹钟函数
注:闹钟只有一个,定时只有一次有效,但是必须根据代码逻辑是否执行判断。

(6)代码示例(alarm)

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/types.h>
#include <unistd.h>

int    main(int argc, char **argv)
{
    
    alarm(5);
    while(1)
    {
        printf("sleep...\n");
        sleep(1);
    }

    system("pause");
    return 0;
}

(7)pause函数

int pause(void);
功能:进程暂停,不再继续执行,除非收到其他信号。

(8)代码示例(pause)

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/types.h>
#include <unistd.h>


int    main(int argc, char **argv)
{
    int i = 0 ;
      while(1)
    {
        printf("work...\n");
        sleep(1);
        i++;
        if(5 == i )
        {
            pause();
        }
    }


    system("pause");
    return 0;
}

4、接收端

每个进程都会对信号作出默认响应,但不是唯一响应。
一般如下三种处理方式:
1、默认处理
2、忽略处理 9,19(9是强制关闭,19是强制暂停)
3、自定义处理 9,19 捕获

以上三种方式的处理需要在如下函数上实现。

(1)signal函数(信号注册函数)(回调函数)

函数原型:void ( *signal(int signum, void (*handler)(int)) ) (int);

typedef void (*sighandler_t)(int);
 ===》void (*xx)(int); == void fun(int);
 ===》xx是 void fun(int) 类型函数的函数指针
 ===》typedef void(*xx)(int)   sighandler_t; ///错误
  typedef int   myint;
 ===>sighandler_t signal(int signum, sighandler_t handler);
 ===> signal(int sig, sighandler_t fun);
 ===> signal(int sig, xxx fun);
 ===>fun 有三个宏表示:SIG_DFL 表示默认处理
                                         SIG_IGN 表示忽略处理
                                         fun     表示自定义处理

(2)代码示例

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/types.h>
#include <unistd.h>

void handle1(int num)
{
    static int i = 0;
    printf("老爸叫你...\n");
    i++;
    if(i ==3)
    {
        signal(SIGUSR1,SIG_IGN); //忽略信号
    }

}
void handle2(int num)
{
    static int i = 0;
    printf("老妈叫你...\n");
    i++;
    if(i ==3)
    {
        signal(SIGUSR2,SIG_DFL); //回复默认信号
    }

}

int    main(int argc, char **argv)
{
    
    signal(SIGUSR1,handle1);
    signal(SIGUSR2,handle2);
    
    while(1)
    {

        printf("i'm playing pid:%d\n",getpid());
        sleep(1);
    }

    system("pause");
    return 0;
}

七、扩展

1、SIGCHLD

        实际上,在子进程结束的时候,会产生一个SIGCHLD信号,信号描述如下,根据man手册可以知道,子进程结束运行,其父进程会收到SIGCHLD信号,该信号的默认处理动作是忽略。

SIGCHLD信号产生的条件主要有以下几个:

子进程终止时;

子进程接收到SIGSTOP信号停止时;

子进程处在停止态,接受到SIGCONT后唤醒时;

2、信号处理函数中的num,代表的是该信号的数字