IO进程——进程间通信

一、进程间通信

1.早期的进程通信方式

        无名管道、有名管道、信号

2.新增的进程间通信方式

        system V IPC对象

        共享内存、信号灯集、消息队列

3.BSD

        socket 套接字

1.1无名管道

1.1.1特点

  1. 只能用于具有近缘关系的进程之间的通信
  2. 半双工的通信方式,具有固定的读端fd[0]和写端fd[1]
  3. 管道可以看成是一种特殊的文件,对于它的读写可以使用文件IO如read、write函数.
  4. 管道是基于文件描述符的通信方式。当一个管道建立时,它会创建两个文件描述符

        fd[0]和fd[1]。其中fd[0]固定用于读管道,而fd[1]固定用于写管道。

1.1.2函数接口

int pipe ( int fd [ 2 ])

功能:创建无名管道

参数:

        文件描述符

        fd[0]:读端

        fd[1]:写端

返回值:

        成功 0

        失败 -1

1.1.3读写特性

1.当管道中无数据时,读操作会阻塞

                管道中无数据时,将写端关闭,读操作会立即返回

2.管道中装满(管道大小64k)数据,写阻塞,一旦有4k空间,写继续

3.只有在管道的读端存在时,向管道中写入数据才有意义。否则,会导致管道破裂,向管道中写入数据的进程将收到内核传来的SIGPIPE信号: (通常Broken pipe错误)。Broken pipe:管道破裂

//将读端关闭
close(fd[0]);
//相关到中写数据
write(fd[1],"hello",5);
//最后一句话没有打印出来,因为它执行到write就已经结束了
//内核发起一个SIGPIPE信号,进程接收到这个信号就结束了
printf("after\n");

1.1.4函数

#include <unistd.h>

int pipe(int fd[2])

功能:创建无名管道

参数:文件描述符 fd[0]:读端 fd[1]:写端

返回值:成功 0

失败 -1

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

int main(int argc, char const *argv[])
{
    int fd[2] = {0};
    char buf[65536] = "";
    if (pipe(fd) < 0)
    {
        perror("pipe error");
        return -1;
    }
    printf("%d %d\n", fd[0], fd[1]); // 3  4

    read(fd[0], buf, 32);
    printf("%s\n", buf);

    read(fd[0], buf, 32);
    printf("%s\n", buf);
    return 0;
}

1.2有名管道

1.2.1特点

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

1.2.2函数接口

#include <sys/types.h>

#include <sys/stat.h>

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

功能:创建有名管道

参数:filename:有名管道文件名

mode:权限

返回值:成功:0

失败:-1,并设置errno号

补充:

        1.当管道文件存在(报错提示 file exists)时的处理方式:

判断errno的值为EEXIST时,只是打印提示语句,并不退出程序

if(errno == EEXIST)

        2.注意代码中出现 errno,需要添加头文件 #include <errno.h>

1.2.3读写特性

1.只写方式,写阻塞(阻塞在文件打开位置),一直到另一个进程把读打开

2.只读方式,读阻塞(阻塞在文件打开位置),一直到另一个进程把写打开

3.可读可写,如果管道中没有数据,读阻塞

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

无名管道

有名管道

使用场景

只能在亲缘关系进程

不相关的任意进程

特点

有固定读端fd[0]写端fd[1]

文件IO进行操作

不支持lseek操作

数据存储在内核空间

在文件系统中存在管道文件

文件IO进行操作

不支持lseek操作

先进先出原则

数据存储在内核空间

操作方式

直接进行读写操作

先打开管道文件,再读写

函数

pipe

mkfifo

1.3信号

1.3.1信号的概念

  • 信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式
  • 信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。
  • 如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。

1.3.2信号的分类

在Linux中,信号被分为不可靠信号和可靠信号,一共64种,可以通过kill -l命令来查看

  • 不可靠信号:也称为非实时信号,不支持排队,信号可能会丢失,比如发送多次相同的信号,进程只能收到一次,信号值取值区间为1~31
  • 可靠信号:也称为实时信号,支持排队,信号不会丢失,发多少次,就可以收到多少次,信号值取值区间为34~64

1.3.3信号产生的方式

  • 对于前台进程,用户可以输入特殊终端字符来发送:比如:输入ctrl c
  • 在终端运行kill命令或者在程序中调用 kill 函数
  • 系统状态变化,比如alarm定时器到期时将引起SIGALRM信号

1.3.4信号的处理方式

  • 忽略信号:不做任何处理
  • 捕获信号:执行自定义的信号处理函数
  • 执行默认操作:Linux系统中对每种信号规定了默认操作,即执行信号默认的功能

1.3.5信号分类

kill -l

SIGKILL:结束进程,不能被忽略不能被捕捉 9

SIGSTOP:结束进程,不能被忽略不能被捕捉 19

SIGCHLD:子进程状态改变时给父进程发的信号,不会结束进程 17

SIGINT:结束进程,对应快捷方式ctrl+c 2

SIGTSTP:暂停信号,对应快捷方式ctrl+z 20

SIGQUIT:退出信号,对应快捷方式ctrl+\ 3

SIGALRM:闹钟信号,alarm函数设置定时,当到设定的时间时,内核会向进程发送此信号结束进程。 14

SIGTERM:结束终端进程,kill 使用时不加数字默认是此信号 15

1.3.6信号函数

#include <signal.h>

int kill(pid_t pid, int sig);

功能:信号发送

参数:pid:指定进程

sig:要发送的信号

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

int main(int argc,char const *argv[])
{
    kill(getpid(),SIGKILL);
    
    while (1);
    return  0;
}
给调用raise这个函数的进程发送信号

#include <signal.h>

int raise(int sig);

功能:进程向自己发送信号

参数:sig:信号

返回值:成功 0

失败 -1

int main(int argc, char const *argv[])
{
    raise(9);
    
    while(1);
    return 0;
}
父子之间进行信号发送:

#include <unistd.h>

unsigned int alarm(unsigned int seconds)

功能:在进程中设置一个定时器

参数:seconds:定时时间,单位为秒

返回值:

如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0

注意:一个进程只能有一个闹钟时间。如果在调用alarm时

已设置过闹钟时间,则之前的闹钟时间被新值所代替

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

int main(int argc, char const *argv[])
{
    int n = alarm(3);
    printf("第一个定时器%d\n", n);

   sleep(1);
    printf("hello\n");

    n = alarm(5);
    printf("第二个定时器%d\n", n);

    while(1);
    
    return 0;
}
进程挂起
int pause ( void );

功能:用于将调用进程挂起,直到收到信号为止。

//去使用while循环的话,它会一直在循环状态,会意字占用CPU资源
//为了避免这种情况我们可以使用
pause();
//用于将调用进程挂起(形成阻塞),直到收到信号位置
信号处理
sighandler_t signal ( int signum , sighandler_t handler );

功能:信号处理函数

参数:signum:要处理的信号

handler:信号处理方式

SIG_IGN:忽略信号

SIG_DFL:执行默认操作

handler:捕捉信号

void handler(int sig){} //函数名可以自定义

返回值:成功:设置之前的信号处理方式

失败:-1

signal函数原型的推到过程

typedef 数据类型 新类型名

typedef void(*sighandler_t)(int)

||

typedef void(*)(int) sighandler_t; 函数指针是不支持这样的书写格式的

||

sighandler_t signal(int signum, void(*)(int) handeler);

||

sighandler_t signal(int signum, void(*handeler)(int));

||

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

||

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

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

void handler(int sig)
{
    printf("hello\n");
}

int main(int argc, char const *argv[])
{
    //执行默认操作
    //signal(SIGINT, SIG_DFL);

    //忽略信号
    //signal(SIGINT, SIG_IGN);

    //捕获信号
    signal(SIGINT, handler);

    pause();
    return 0;
}

1.3.7信号的处理过程

程序运行在用户空间时->进程由于系统调用或中断进入内核->转向用户空间执行信号处理函数->信号处理函数完毕后进入内核->返回用户空间继续执行程序

 

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

void handler(int sig)
{
    if (sig == SIGINT)
    {
        printf("hello\n");
    }
    else
    {
        printf("no\n");
    }
}

int main(int argc, char const *argv[])
{
    // 执行默认操作
    // signal(SIGINT, SIG_DFL);

    // 忽略信号
    // signal(SIGINT, SIG_IGN);

    // 捕获信号
    signal(SIGINT, handler);

    pause();
    return 0;
    return 0;
}

  • 23
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值