首页 > 系统相关 >进程间的通信(有名管道、信号)

进程间的通信(有名管道、信号)

时间:2024-09-01 18:54:08浏览次数:7  
标签:int 通信 管道 有名 信号 printf 进程 fd include

1.有名管道

1.1 特点

  1. 有名管道可以使互不相关的两个进程互相通信。
  2. 有名管道可以通过路径名来指出,并且在文件系统中可见,但内容存放在内存中。但是读写数据不会存在文件中,而是在管道中。
  3. 进程通过文件IO来操作有名管道。
  4. 有名管道遵循先进先出规则
  5. 不支持如lseek() 操作

1.2函数接口

int mkfifo(const char *filename,mode_t mode);
功能:创健有名管道
参数:filename:有名管道文件名
       mode:权限
返回值:成功:0
       失败:-1,并设置errno号
注意对错误的处理方式:
如果错误是file exist时,注意加判断,如:if(errno == EEXIST)

注:函数只是在路径下创建管道文件,往管道中写的数据依然写在内核空间。

先创建有名管道,然后用文件IO操作:打开、读写和关闭。

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

int main(int argc, char const *argv[])
{
    if (mkfifo("./fifo", 0777) < 0)
    {
        if (errno == EEXIST)   //如果错误号信息是已存在则打印提示语句
            printf("file exist!\n");
        else
        {
            perror("mkfifo err");
            return -1;
        }
    }
    printf("mkfifo success\n");

    return 0;
}

1.3 注意事项

  1. 只写方式打开阻塞,一直到另一个进程把读打开
  2. 只读方式打开阻塞,一直到另一个进程把写打开
  3. 可读可写,如果管道中没有数据,读阻塞
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    char buf[32] = "";
    if (mkfifo("./fifo", 0777) < 0)
    {
        if (errno == EEXIST) //如果错误号信息是已存在则打印提示语句
            printf("file exist!\n");
        else
        {
            perror("mkfifo err");
            return -1;
        }
    }
    printf("mkfifo success\n");

    //打开文件
    int fd = open("./fifo", O_RDWR);

    //读写操作
    write(fd, "hello", 5);
    read(fd, buf, 32);
    printf("%s\n", buf);
    return 0;
}

练习题:通过两个进程实现cp功能。

./input srcfile

./output destfile

input.c 读源文件

//创建有名管道

//打开管道文件和源文件

//循环读源文件,写入管道

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

int main(int argc, char const *argv[])
{
    int fd_fifo, fd_file;
    char buf[32] = "";
    ssize_t s;
    if (mkfifo("fifo", 0666) < 0)
    {
        if (errno == EEXIST) 
            printf("file exist!\n"); 
        else
        {
            perror("mkfifo err");
            return -1;
        }
    }
    printf("mkfifo success!\n");

    //打开文件
    fd_fifo = open("fifo", O_WRONLY);
    fd_file = open(argv[1], O_RDONLY);
    
    if (fd_fifo < 0)
    {
        perror("open err");
        return -1;
    }

    if (fd_file < 0)
    {
        perror("open err");
        return -1;
    }

    //读源文件,写入管道
    while (1)
    {
        s = read(fd_file,buf,32);    //从文件读到buf中
        if(s == 0)
            break;
        write(fd_fifo,buf,s);        //把buf中数据写进有名管道
    }
    
    close(fd_fifo);
    close(fd_file);
    return 0;
}

ouput.c 写目标文件

//创建有名管道

//打开管道文件和目标文件

//循环读管道,写入目标文件

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

int main(int argc, char const *argv[])
{
    int fd_fifo, fd_file;
    char buf[32] = "";
    ssize_t s;
    if (mkfifo("fifo", 0666) < 0)
    {
        if (errno == EEXIST)
            printf("file exist!\n");
        else
        {
            perror("mkfifo err");
            return -1;
        }
    }
    printf("mkfifo success!\n");

    //打开文件
    fd_fifo = open("fifo", O_RDONLY);
    fd_file = open(argv[1], O_WRONLY | O_CREAT |O_TRUNC,0666);
    
    if (fd_fifo < 0)
    {
        perror("open err");
        return -1;
    }

    if (fd_file < 0)
    {
        perror("open err");
        return -1;
    }

    //读管道,写入目标文件
    while (1)
    {
        s = read(fd_fifo,buf,32);    //从有名管道中读数据到buf
        if(s == 0)
            break;
        write(fd_file,buf,s);        //把buf中数据写到
    }
    
    close(fd_fifo);
    close(fd_file);
    return 0;
}

1.4 有名管道和无名管道的区别

无名管道

有名管道

使用场景

具有亲缘关系的进程间

不相干的进程可以使用

特点

半双工通信方式

固定的读端fd[0]和写端fd[1]

看作一种特殊的文件可以通过文件操作

再文件系统中会存在管道文件,数据放在内核空间中

通过文件IO进行操作

遵循先进先出,不支持lseek操作

函数

pipe()

直接read/write

mkfifo()

先打开open,再读写read/write

读写特性

当管道中无数据读阻塞

当管道中写满时写阻塞

关闭读端,往管道中写会管道破裂

只写方式打开会阻塞,直到另一个进程把读打开

只读方式打开会阻塞,直到另一个进程把写打开

可读可写打开,如果管道中无数据读阻塞

2.信号

kill -l: 显示系统中的信号

kill -num PID: 给某个进程发送信号

2.1 概念

1)信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式

2)信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。

3)如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。

信号的生命周期:

2.2信号的响应方式

1)忽略信号:对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP。

2)捕捉信号:定义信号处理函数,当信号发生时,执行相应的处理函数。

3)执行缺省操作:Linux对每种信号都规定了默认操作

2.3信号种类

SIGINT(2):中断信号,Ctrl-C 产生,用于中断进程

SIGQUIT(3):退出信号, Ctrl-\ 产生,用于退出进程并生成核心转储文件

SIGKILL(9):终止信号,用于强制终止进程。此信号不能被捕获或忽略。

SIGALRM(14):闹钟信号,当由 alarm() 函数设置的定时器超时时产生。

SIGTERM(15):终止信号,用于请求终止进程。此信号可以被捕获或忽略。termination

SIGCHLD(17):子进程状态改变信号,当子进程停止或终止时产生。

SIGCONT(18):继续执行信号,用于恢复先前停止的进程。

SIGSTOP(19):停止执行信号,用于强制停止进程。此信号不能被捕获或忽略。

SIGTSTP(20):键盘停止信号,通常由用户按下 Ctrl-Z 产生,用于请求停止进程。

2.4 函数接口

2.4.1 信号发送和挂起

#include <signal.h>
int kill(pid_t pid, int sig);
功能:信号发送
参数:pid:指定进程
   sig:要发送的信号
返回值:成功 0     
       失败 -1

int raise(int sig);
功能:进程向自己发送信号
参数:sig:信号
返回值:成功 0   
       失败 -1
相当于:kill(getpid(), sig);
       
int pause(void);
功能:用于将调用进程挂起,直到收到被捕获处理的信号为止。
#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    //kill(getpid(), SIGKILL);  //给进程发送信号,此例子是给当前进程发送SIGKILL信号
    // raise(SIGKILL);  //给当前进程发送SIGKILL信号,等同于kill(getpid(), SIGKILL);  
    // while (1);

    pause();  //将进程挂起,作用类似死循环但是不占用CPU
    return 0;
}

2.4.2 定时器

man 2 alarm

unsigned int alarm(unsigned int seconds)
功能:在进程中设置一个定时器。当定时器指定的时间到了时,它就向进程发送SIGALARM信号。
参数:seconds:定时时间,单位为秒
返回值:如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。
注意:一个进程只能有一个闹钟时间。如果在调用alarm时已设置过闹钟时间,则之前的闹钟时间被新值所代替。
常用操作:取消定时器alarm(0),返回旧闹钟余下秒数。

系统默认对SIGALRM(闹钟到点后内核发送的信号)信号的响应: 如果不对SIGALRM信号进行捕捉或采取措施,默认情况下,闹钟响铃时刻会退出进程。

例子:

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

int main(int argc, char const *argv[])
{
    printf("%d\n", alarm(10));  //设定闹钟5秒后发送闹钟信号
    sleep(1);                                       //睡眠1秒,此时闹钟还剩9秒
    printf("%d\n", alarm(3));    //打印剩余的9秒,设新闹钟3秒后发送闹钟信号
    pause(); //为了不让进程结束,等待SIGALRM信号产生,产生之后结束当前进程
    return 0;
}

2.4.3 信号处理函数signal()

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:信号处理函数
参数:signum:要处理的信号
      handler:信号处理方式
          SIG_IGN:忽略信号  (忽略 ignore)
          SIG_DFL:执行默认操作 (默认 default)
          handler:捕捉信号 (handler为函数名,可以自定义)
     void handler(int sig){} //函数名可以自定义, 参数为要处理的信号
返回值:成功:设置之前的信号处理方式
      失败:-1

补充: typedef 给数据类型重命名

#include <stdio.h>

//给普通数据类型int重命名
typedef int size4;        

//给指针类型int* 重命名
typedef int *int_p;       

//给数组类型int [10]重命名
typedef int intArr10[10]; 

//给函数指针void (*)()重命名
typedef void (*fun_p)(); 

void fun()
{
    printf("fun\n");
}

int main(int argc, char const *argv[])
{
    size4 a = 10;             //相当于int a=10;
    int_p p = &a;             //相当于int* p=&a;
    intArr10 arr = {1, 2, 3}; //相当于int arr[10]={1,2,3};
    fun_p fp = fun;           //相当于 void (*fp)()=fun;
    printf("%d\n", *p);
    printf("%d\n", arr[0]);
    fp();

    return 0;
}

总而言之,定义变量的变量名写在哪里,用typedef给数据类型重命名的新名字就写在哪里。然后使用新名字定义变量的格式直接就可以为:新名字 变量名;

例子:

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

void handler(int sig) //参数sig代表要处理的信号
{
    if (sig == SIGINT)
        printf("ctrl C: %d\n", sig);
    else if (sig == SIGTSTP)
        printf("ctrl Z: %d\n", sig);
}

int main(int argc, char const *argv[])
{
    signal(SIGINT, SIG_IGN);  //对SIGINIT信号设置忽略方式处理
    //signal(SIGINT,SIG_DFL);  //对SIGINIT信号设置缺省方式处理,也就是默认操作
    signal(SIGINT, handler); //对SIGINIT信号设置捕捉方处理,也就是自定义处理方式
    signal(SIGTSTP, handler);

    //while (1); //为了让进程不要结束,因为等到信号真的来才能验证,不然进程就结束了
    pause();  //收到被捕获处理的信号时会结束挂起
    return 0;
}

(思考题)用信号的知识实现司机和售票员问题。

1)售票员捕捉SIGINT(代表开车)信号,向司机发送SIGUSR1信号,司机打印(let's gogogo)

2)售票员捕捉SIGQUIT(代表停车)信号,向司机发送SIGUSR2信号,司机打印(stop the bus)

3)司机捕捉SIGTSTP(代表到达终点站)信号,向售票员发送SIGUSR1信号,售票员打印(please get off the bus)

4)司机等待售票员下车,之后司机再下车。

分析:司机(父进程)、售票员(子进程)

售票员:

捕捉: SIGINT SIGQUIT SIGUSR1

司机:SIGTSTP

捕捉: SIGUSR1 SIGUSR2 SIGTSTP

忽略:SIGINT SIGQUIT

参考代码:

#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

pid_t pid;
void saler(int sig)
{
    if (sig == SIGINT)
        kill(getppid(), SIGUSR1);
    if (sig == SIGQUIT)
        kill(getppid(), SIGUSR2);
    if (sig == SIGUSR1)
    {
        printf("pls get off the bus!\n");
        exit(0);
    }
}

void driver(int sig)
{
    if (sig == SIGUSR1)
        printf("let's gogogo!!!~~\n");
    if (sig == SIGUSR2)
        printf("stop the bus!\n");
    if (sig == SIGTSTP)
    {
        kill(pid, SIGUSR1);
        wait(NULL);
        exit(0); //司机等待售票员下车之后再下车
    }
}

int main(int argc, char const *argv[])
{
    pid = fork();
    if (pid < 0)
    {
        perror("fork err");
        return -1;
    }
    else if (pid == 0)
    {
        printf("i am saler!\n");
        signal(SIGINT, saler);
        signal(SIGQUIT, saler);
        signal(SIGUSR1, saler);
        signal(SIGTSTP, SIG_IGN);
    }
    else
    {
        printf("i am driver!\n");
        signal(SIGUSR1, driver);
        signal(SIGUSR2, driver);
        signal(SIGTSTP, driver);
        signal(SIGINT, SIG_IGN);
        signal(SIGQUIT, SIG_IGN);
    }

    while (1)
        pause(); //不能发送一个信号就结束进程,所以可以循环挂起不占用CPU

    return 0;
}

最后,我在文末跟大家说一下,最近更新的很多函数,记不住也没关系,其实记不住也正常,大家只需要明白他们的用法就可以了,到时候会查手册调用就可以了。

标签:int,通信,管道,有名,信号,printf,进程,fd,include
From: https://blog.csdn.net/2301_77143270/article/details/141752900

相关文章

  • IO进程day06(进程间通信、信号、共享内存)
    目录【1】进程间通信IPC1》进程间通信方式2》无名管道1>特点2>函数接口3>注意事项练习:父子进程实现通信,父进程循环从终端输入数据,子进程循环打印数据,当输入quit结束。3》有名管道 1>特点2>函数接口3>注意事项 练习:通过两个进程实现cp功能 4>有名管......
  • day10(IO进程)进程间的通信---共享内存
    目录1.特点2.步骤3.函数接口4.命令1.特点1)共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝。2)为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间。进程就可以直接读......
  • STM32学习笔记,SPI通信协议(理论部分)
    SPI通信和I2C通信差不多,两个协议的目的都一样,都是实现主控芯片和各种外挂芯片之间的数据交流;有了数据交流的能力,主控芯片就可以挂载并操纵各式各样的外部芯片,来实现一个功能更强大的控制系统;课程安排与I2C通信一样,先学习SPI协议的软硬件规定;先用软件模拟的SPI,实现读写W25Q64......
  • 进程通信
    通信特点单工通信发送方和接收方固定,不会改变半双工双向通信,但不能同时进行全双工双向通信,并且可以同时进行进程通信目的:1.数据传输:一个进程需要将它的数据发送给另一个进程2.资源共享:多个进程之间共享同样的资源3.通知事件:一个进程需要向另一个或一组......
  • 进程间的通信(无名管道)
    进程间通信IPCInterProcessCommunication1.进程间通信方式1.早期的进程间通信:无名管道(pipe)、有名管道(fifo)、信号(signal)2.systemVPIC:共享内存(sharememory)、信号灯集(semaphore)、消息队列(messagequeue)3.BSD:套接字(socket)2.无名管道2.1特点只......
  • Filter管道
    usingCronos;usingNewtonsoft.Json;usingSystem.Collections;usingSystem.Collections.Concurrent;usingSystem.Collections.Generic;usingSystem.Linq.Expressions;usingSystem.Reflection;usingSystem.Threading;namespaceConsoleApp1{internalcla......
  • 并行程序设计基础——组通信(3)
    目录一、组归约二、归约并散发三、扫描四、π值计算五、不同类型归约操作的对比六、不正确的组通信方式七、MINLOC和MAXLOC八、用户自定义归约操作九、小结    前两节我们介绍了组通信中常用的一对多、多对一以及多对多接口调用,本节继续对其余组通信操作进......
  • 进程间通信----管道篇
    目录一丶  无名管道1. 特点2. 读写特性3. 函数接口二丶有名管道1.特点:2.函数接口3.读写特性一丶  无名管道1. 特点        1. 只能用于具有亲缘关系的进程之间的通信        2. 半双工的通信模式,具有固定的读端和写端   ......
  • 【ROS教程】话题通信
    @目录1.流程2.自定义发布数据2.1std_msgs内置类型2.2编写.msg文件2.3修改package.xml文件2.3.1完整的package.xml文件2.4修改CMakeLists.txt文件2.4.1修改find_package指令2.4.2添加add_message_files指令2.4.3添加generate_messages指令2.4.4修改catkin_package指令2.5......
  • 数据通信的一些基础概念
    数据通信数据通信,即设备之间传递数据信息的过程。数据通信方式根据数据通信方式不同可分为:1、串行通信,即数据逐位按照顺序以此传输。2、并行通信,即数据各位可以通过多条线同时传输。数据传输方向根据数据传输方向不同,可分为:1、全双工,指数据可以同时进行双向传输,日常的......