信号与高级IO

Linux中的信号

信号相关的定义在这里,如果是32位系统是这里usr/include/i386-linux-gnu/bits/signum.h
如果是64位系统在这里usr/include/x86_64-linux-gnu/bits/signum.h

什么是信号
(1)信号的目的:用来通信
(2)信号是异步的(对比硬件中断)
(3)信号本质上是int型数字编号(事先定义好的)
信号由谁发出
(1)用户在终端按下按键
(2)硬件异常后由操作系统内核发出信号
(3)用户使用kill命令向其他进程发出信号
(4)某种软件条件满足后也会发出信号,如alarm闹钟时间到会产生SIGALARM信号,向一个读端已经关闭的管道write时会产生SIGPIPE信号
信号由谁处理、如何处理
(1)忽略信号
(2)捕获信号(信号绑定了一个函数)
(3)默认处理(当前进程没有明显的管这个信号,默认:忽略或终止进程)
常见信号介绍

信号数字功能
SIGINT2Ctrl+C时OS送给前台进程组中每个进程
SIGABRT6调用abort函数,进程异常终止
SIGPOLL SIGIO8指示一个异步IO事件,在高级IO中提及
SIGKILL9杀死进程的终极办法
SIGSEGV11无效存储访问时OS发出该信号
SIGPIPE13涉及管道和socket
SIGALRM14涉及alarm函数的实现
SIGTERM15kill命令发送的OS默认终止信号
SIGCHILD17子进程终止或停止时OS向其父进程发此信号
SIGUSR110用户自定义信号,作用和意义由应用自己定义
SIGUSR212用户自定义信号,作用和意义由应用自己定义

进程对信号的处理
signal函数介绍
这是一个API

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

用signal函数处理SIGINT信号
(1)默认处理
(2)忽略处理
(3)捕获处理
细节:

(1)signal函数绑定一个捕获函数后信号发生后会自动执行绑定的捕获函数,并且把信号编号作为传参传给捕获函数

(2)signal的返回值在出错时为SIG_ERR(SIG_ERR其实就是sighandler_t类型的-1),绑定成功时返回旧的捕获函数
SIG_DFL:sighandler_t类型的0,是默认处理的意思
SIG_IGN:sighandler_t类型的1,忽略处理,这几个宏用来给signal函数传的第二个参数,第二个参数也可以是自己实现的函数,用来捕获到信号后执行。
signal函数的优点和缺点

(1)优点:简单好用,捕获信号常用
(2)缺点:无法简单直接得知之前设置的对信号的处理方法
sigaction函数介绍
(1)2个都是API,但是sigaction比signal更具有可移植性
(2)用法关键是2个sigaction指针
sigaction比signal好的一点:sigaction可以一次得到设置新捕获函数和获取旧的捕获函数(其实还可以单独设置新的捕获或者单独只获取旧的捕获函数),而signal函数不能单独获取旧的捕获函数而必须在设置新的捕获函数的同时才获取旧的捕获函数。

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

typedef void (*sighandler_t)(int);


void func(int sig)//捕获函数
{
    if (SIGINT != sig)
        return;
    
    printf("func for signal: %d.\n", sig);
}


int main(void)
{
    sighandler_t ret = (sighandler_t)-2;//给ret赋初值为-2。
    //signal(SIGINT, func);         //就是用来捕获信号的,信号发生时执行func函数
    //signal(SIGINT, SIG_DFL);    // 指定信号SIGINT为默认处理     
    ret = signal(SIGINT, SIG_IGN);        // 指定信号SIGINT为忽略处理     
    if (SIG_ERR == ret)
    {
        perror("signal:");
        exit(-1);
    }
    
    printf("before while(1)\n");
    while(1);
    printf("after while(1)\n");
    
    return 0;
}

alarm和pause函数

alarm函数

(1)内核以API形式提供的闹钟
pause函数
(1)内核挂起
pause函数的作用就是让当前进程暂停运行,交出CPU给其他进程去执行。当当前进程进入pause状态后当前进程会表现为“卡住、阻塞住”,要退出pause状态当前进程需要被信号唤醒。

高级IO

非阻塞IO
1、阻塞与非阻塞
之前也学过这个概念,阻塞就是会被阻止住,程序不会再继续往下执行,知道这个阻塞函数执行完才可以。
非阻塞就是不用等待,直接往下执行。
为什么有阻塞式
(1)常见的阻塞:wait、pause、sleep等函数;read或write某些文件时
非阻塞
如何实现非阻塞IO访问:O_NONBLOCK和fcntl
阻塞式IO的困境
1、程序中读取键盘
2、程序中读取鼠标
通过cat /dev/input/mouse0判断到底哪个是鼠标对应的文件
3、程序中同时读取键盘和鼠标

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


int main(void)
{
    // 读取鼠标     
    int fd = -1;
    char buf[200];
    
    fd = open("/dev/input/mouse1", O_RDONLY);
    if (fd < 0)
    {
        perror("open:");
        return -1;
    }
    
    memset(buf, 0, sizeof(buf));
    printf("before 鼠标 read.\n");
    read(fd, buf, 50);
    printf("鼠标读出的内容是:[%s].\n", buf);
    
    
    // 读键盘     
    memset(buf, 0, sizeof(buf));
    printf("before 键盘 read.\n");
    read(0, buf, 5);
    printf("键盘读出的内容是:[%s].\n", buf);
    
    
    return 0;
}


/* 
int main(void) 
{ 
// 读取鼠标 
    int fd = -1; 
    char buf[200]; 
    fd = open("/dev/input/mouse1", O_RDONLY); 
    if (fd < 0) 
    { 
        perror("open:"); 
        return -1; 
    } 
    memset(buf, 0, sizeof(buf)); 
    printf("before read.\n"); 
    read(fd, buf, 50); 
    printf("读出的内容是:[%s].\n", buf);
    return 0; 
} 
*/


/* 
int main(void) 
{ 
    // 读取键盘 
    // 键盘就是标准输入,
    stdin char buf[100];
    memset(buf, 0, sizeof(buf));
    printf("before read.\n"); 
    read(0, buf, 5); 
    printf("读出的内容是:[%s].\n", buf);
    return 0; 
} 
*/

并发式IO的解决方案
1、非阻塞式IO
2、多路复用IO
3、异步通知(异步IO)
因为上面这样不能很好的解决同时读取鼠标和键盘的问题,所以我们有了一下的解决方案。
IO多路复用原理
何为IO多路复用
(1)IO multiplexing(IO多路复用的英文)
(2)用在什么地方?多路非阻塞式IO。
(3)select和poll
(4)外部阻塞式,内部非阻塞式自动轮询多路阻塞式IO
select函数
poll函数
select函数和poll函数其实差不多,只是因为unix当时出现了两个分支,写出来的两个东西,poll函数好像是由加州伯克利分校写出来的。
用select函数实现同时读取键盘鼠标

#include <stdio.h> 
#include <unistd.h> 
#include <string.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <sys/select.h> 
#include <sys/time.h> 

int main(void)
{
    // 读取鼠标     
    int fd = -1, ret = -1;
    char buf[200];
    fd_set myset;
    struct timeval tm;
    
    fd = open("/dev/input/mouse1", O_RDONLY);
    if (fd < 0)
    {
        perror("open:");
        return -1;
    }
    
    // 当前有2个fd,一共是fd一个是0     
    // 处理myset     
    FD_ZERO(&myset);
    FD_SET(fd, &myset);
    FD_SET(0, &myset);
    
    tm.tv_sec = 10;
    tm.tv_usec = 0;

    ret = select(fd+1, &myset, NULL, NULL, &tm);
    if (ret < 0)
    {
        perror("select: ");
        return -1;
    }
    else if (ret == 0)
    {
        printf("超时了\n");
    }
    else
    {
        // 等到了一路IO,然后去监测到底是哪个IO到了,处理之         
        if (FD_ISSET(0, &myset))
        {
            // 这里处理键盘             
            memset(buf, 0, sizeof(buf));
            read(0, buf, 5);
            printf("键盘读出的内容是:[%s].\n", buf);
        }
        
        if (FD_ISSET(fd, &myset))
        {
            // 这里处理鼠标             
            memset(buf, 0, sizeof(buf));
            read(fd, buf, 50);
            printf("鼠标读出的内容是:[%s].\n", buf);
        }
    }

    return 0;
}

用poll函数实现同时读取键盘鼠标

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


int main(void)
{
    // 读取鼠标     
    int fd = -1, ret = -1;
    char buf[200];
    struct pollfd myfds[2] = {0};
    
    fd = open("/dev/input/mouse1", O_RDONLY);
    if (fd < 0)
    {
        perror("open:");
        return -1;
    }
    
    // 初始化我们的pollfd     
    myfds[0].fd = 0;            // 键盘     
    myfds[0].events = POLLIN;    // 等待读操作     
    
    myfds[1].fd = fd;            // 鼠标     
    myfds[1].events = POLLIN;    // 等待读操作     

    ret = poll(myfds, fd+1, 10000);
    if (ret < 0)
    {
        perror("poll: ");
        return -1;
    }
    else if (ret == 0)
    {
        printf("超时了\n");
    }
    else
    {
        // 等到了一路IO,然后去监测到底是哪个IO到了,处理之         
        if (myfds[0].events == myfds[0].revents)
        {
            // 这里处理键盘             
            memset(buf, 0, sizeof(buf));
            read(0, buf, 5);
            printf("键盘读出的内容是:[%s].\n", buf);
        }
        
        if (myfds[1].events == myfds[1].revents)
        {
            // 这里处理鼠标             
            memset(buf, 0, sizeof(buf));
            read(fd, buf, 50);
            printf("鼠标读出的内容是:[%s].\n", buf);
        }
    }

    return 0;
}

异步IO
何为异步IO
(1)几乎可以认为:异步IO就是操作系统用软件实现的一套中断响应系统。类似于硬件的中断,只不过这是软件的
(2)异步IO的工作方法是:我们当前进程注册一个异步IO事件(使用signal注册一个信号SIGIO的处理函数),然后当前进程可以正常处理自己的事情,当异步事件发生后当前进程会收到一个SIGIO信号从而执行绑定的处理函数去处理这个异步事件。
涉及的函数:
(1)fcntl(F_GETFL、F_SETFL、O_ASYNC、F_SETOWN)
(2)signal或者sigaction(SIGIO)

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



int mousefd = -1;


// 绑定到SIGIO信号,在函数内处理异步通知事件 
void func(int sig)
{
    char buf[200] = {0};
    
    if (sig != SIGIO)
        return;

    read(mousefd, buf, 50);
    printf("鼠标读出的内容是:[%s].\n", buf);
}

int main(void)
{
    // 读取鼠标     
    char buf[200];
    int flag = -1;
    
    mousefd = open("/dev/input/mouse1", O_RDONLY);
    if (mousefd < 0)
    {
        perror("open:");
        return -1;
    }    
    // 把鼠标的文件描述符设置为可以接受异步IO     
    flag = fcntl(mousefd, F_GETFL);
    flag |= O_ASYNC;
    fcntl(mousefd, F_SETFL, flag);
    // 把异步IO事件的接收进程设置为当前进程     
    fcntl(mousefd, F_SETOWN, getpid());
    
    // 注册当前进程的SIGIO信号捕获函数     
    signal(SIGIO, func);
    
    // 读键盘     
    while (1)
    {
        memset(buf, 0, sizeof(buf));
        //printf("before 键盘 read.\n");         
                read(0, buf, 5);
        printf("键盘读出的内容是:[%s].\n", buf);
    }
        
    return 0;
}

存储映射IO
1、mmap函数
2、LCD显示和IPC之共享内存
3、存储映射IO的特点
(1)共享而不是复制,减少内存操作
(2)处理大文件时效率高,小文件不划算

Last modification:October 8th, 2019 at 12:20 am
如果觉得我的文章对你有用,请随意赞赏

Leave a Comment