首页 > 其他分享 >重头开始嵌入式第三十一天(IO多路复用)

重头开始嵌入式第三十一天(IO多路复用)

时间:2024-09-02 18:21:39浏览次数:11  
标签:set IO 多路复用 epoll int 描述符 fd 第三十一 include

目录

1.IO多路复用

1.IO模型

1、阻塞IO ===》最常用 默认设置

2、非阻塞IO ===》在阻塞IO的基础上调整其为不再阻塞等待。 在程序执行阶段调整文件的执行方式为非阻塞:

3.信号驱动io 

4.并发 

 5.IO 多路复用 ===》并发服务器 ===》TCP协议

1、select循环服务器 ===> 用select函数来动态检测有数据流动的文件描述符函数原型:

2.poll

3.epoll

4.select poll epoll的区别


1.IO多路复用


定义:单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力

作用:
应用程序通常需要处理来自多条事件流中的事件,比如我现在用的电脑,需要同时处理键盘鼠标的输入、中断信号等等事件,再比如web服务器如nginx,需要同时处理来来自N个客户端的事件。
逻辑控制流在时间上的重叠叫做 并发
而CPU单核在同一时刻只能做一件事情,一种解决办法是对CPU进行时分复用(多个事件流将CPU切割成多个时间片,不同事件流的时间片交替进行)。在计算机系统中,我们用线程或者进程来表示一条执行流,通过不同的线程或进程在操作系统内部的调度,来做到对CPU处理的时分复用。这样多个事件流就可以并发进行,不需要一个等待另一个太久,在用户看起来他们似乎就是并行在做一样。

使用并发处理的成本:
线程/进程创建成本
CPU切换不同线程/进程成本 Context Switch
多线程的资源竞争

有没有一种可以在单线程/进程中处理多个事件流的方法呢?一种答案就是IO多路复用。

因此IO多路复用解决的本质问题是在用更少的资源完成更多的事。


1.IO模型


1、阻塞IO  
2、非阻塞IO  EAGAIN  忙等待 errno
3、信号驱动IO  SIGIO 用的相对少(了解)
4、并行模型 进程,线程
5, IO多路复用  select、poll、epoll


1、阻塞IO ===》最常用 默认设置
 


2、非阻塞IO ===》在阻塞IO的基础上调整其为不再阻塞等待。
 在程序执行阶段调整文件的执行方式为非阻塞:


fcntl()动态调整文件的阻塞属性
函数原型:

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );

功能:修改指定文件的属性信息。
参数:fd 要调整的文件描述符
  cmd 要调整的文件属性宏名称
  ... 可变长的属性值参数。
返回值:成功  不一定,看cmd
失败  -1;

eg:修改文件的非阻塞属性:
int flag ;
flag  = fcntl(fd,F_GETFL,0);  ///获取fd文件的默认属性到flag变量中。
flag  = flag | O_NONBLOCK;    ///将变量的值调整并添加非阻塞属性
fcntl(fd,F_SETFL,flag);       ///将新属性flag设置到fd对应的文件生效。
以上代码执行后的阻塞IO将变成非阻塞方式。
比如以下代码

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

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

    int ret = mkfifo("myfifo1",0666);
    if(-1 == ret)
    {
        if( EEXIST!= errno )
        {
            perror("mkfifo");
            return 1;
        }
    }
  int fd_r = open("myfifo1",O_RDONLY);
    if(-1 == fd_r)
    {
        perror("open");
        return 1;
    }

    int flag = fcntl(fd_r,F_GETFL);
    fcntl(fd_r,F_SETFL,flag|O_NONBLOCK);

    flag = fcntl( 0,F_GETFL);
    fcntl(0,F_SETFL,flag|O_NONBLOCK);
    while(1)
    {
        char buf[128]={0};
        if(read(fd_r,buf,sizeof(buf))>0)
        {
            printf("fifo:%s\n",buf);
        }

        bzero(buf,sizeof(buf));
        if(fgets(buf,sizeof(buf),stdin))
        {
            printf("terminal:%s\n",buf);
        }
    }
    return 0;
}



3.信号驱动io 


文件描述符需要追加 O_ASYNC 标志。
设备有io事件可以执行时,内核发送SIGIO信号。

1.追加标志
int flag ;
flag  = fcntl(fd,F_GETFL,0);
fcntl(fd,F_SETFL,flag | O_ASYNC);    
2.设置信号接收者
fcntl(fd,F_SETOWN,getpid());//常用设置
3.对信号进行捕获
signal(SIGIO,myhandle);//

比如:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
int fd_r;
void handle(int num)
{
    char buf[128]={0};
    read(fd_r,buf,sizeof(buf));
    printf("fifo:%s\n",buf);
    return ;
}
int main(int argc, char *argv[])
{
    signal(SIGIO,handle);

    int ret = mkfifo("myfifo1",0666);
    if(-1 == ret)
    {
        if( EEXIST!= errno )
        {
            perror("mkfifo");
            return 1;
        }
    }
    fd_r = open("myfifo1",O_RDONLY);
    if(-1 == fd_r)
    {
        perror("open");
        return 1;
    }

    //给管道设置信号驱动
    int flag = fcntl(fd_r,F_GETFL);
    fcntl(fd_r,F_SETFL,flag| O_ASYNC);
    //如果有写管道,本进程作为sigio信号的接收者
    fcntl(fd_r,F_SETOWN,getpid());
    while(1)
    {
        char buf[128]={0};
        bzero(buf,sizeof(buf));
        fgets(buf,sizeof(buf),stdin);
        printf("terminal:%s\n",buf);
        
    }
    return 0;
}


4.并发 


1.进程
2.线程

 5.IO 多路复用 ===》并发服务器 ===》TCP协议

1、select循环服务器 ===> 用select函数来动态检测有数据流动的文件描述符

函数原型:

#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout);

功能:完成指定描述符集合中有效描述符的动态检测。
  该函数具有阻塞等待功能,在函数执行完毕后
  目标测试集合中将只保留最后有数据的描述符。

参数:nfds 描述符的上限值,一般是链接后描述符的最大值+1;
  readfds 只读描述符集
  writefds 只写描述符集
  exceptfds 异常描述符集
  以上三个参数都是 fd_set * 的描述符集合类型
  timeout  检测超时 如果是NULL表示一直检测不超时 。

返回值:超时 0
失败  -1
成功 >0


为了配合select函数执行,有如下宏函数:
void FD_CLR(int fd, fd_set *set);
功能:将指定的set集合中编号为fd的描述符号删除。

int  FD_ISSET(int fd, fd_set *set);
功能:判断值为fd的描述符是否在set集合中,
  如果在则返回真,否则返回假。

void FD_SET(int fd, fd_set *set);
功能:将指定的fd描述符,添加到set集合中。

void FD_ZERO(fd_set *set);
功能:将指定的set集合中所有描述符删除。
 

实例如下:

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

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

    int ret = mkfifo("myfifo1",0666);
    if(-1 == ret)
    {
        if( EEXIST!= errno )
        {
            perror("mkfifo");
            return 1;
        }
    }
  int fd_r = open("myfifo1",O_RDONLY);
    if(-1 == fd_r)
    {
        perror("open");
        return 1;
    }
    //1 create set 
    fd_set rd_set,tmp_set; //read set 
    FD_ZERO(&rd_set);
    FD_ZERO(&tmp_set);
    
    // 2. add fd 
    FD_SET(0,&tmp_set);
    FD_SET(fd_r,&tmp_set);
    while(1)
    {
        //6.clean flag
        rd_set = tmp_set;
        char buf[128]={0};
        //3 wait event 
        select(fd_r+1,&rd_set,NULL,NULL,NULL );
        //4 找到对应的fd
        if(FD_ISSET(fd_r,&rd_set))
        {
            read(fd_r,buf,sizeof(buf));
            printf("fifo:%s\n",buf);
        }

        if(FD_ISSET(0,&rd_set))
        {
            bzero(buf,sizeof(buf));
            fgets(buf,sizeof(buf),stdin);
            printf("terminal:%s\n",buf);
        }
    }
    return 0;
}

2.poll

在计算机编程中, poll 函数也是一种用于实现 I/O 多路复用的机制。

一、作用

与 select 函数类似, poll 函数可以监视多个文件描述符,等待其中的一个或多个文件描述符变为可读、可写或有异常情况发生,从而避免阻塞在单个文件描述符的操作上,提高程序的效率。

二、函数原型及参数

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

-  fds :是一个 pollfd 结构类型的数组,每个元素代表一个被监视的文件描述符及其状态。

-  nfds :表示 fds 数组中的元素个数。

-  timeout :指定等待的时间(以毫秒为单位)。如果为 0,表示立即返回;如果为 -1,表示无限期等待,直到有文件描述符状态改变。

三、使用场景

与 select 函数类似,广泛应用于网络编程和需要同时处理多个文件描述符的场景,如服务器程序同时处理多个客户端连接。

四、示例用法

#include <stdio.h>

#include <poll.h>

#include <unistd.h>



int main() {

    struct pollfd fds[2];

    int ret;



    // 监视标准输入(文件描述符为 0)和标准输出(文件描述符为 1)

    fds[0].fd = 0;

    fds[0].events = POLLIN;

    fds[1].fd = 1;

    fds[1].events = POLLOUT;



    ret = poll(fds, 2, 5000);

    if (ret == -1) {

        perror("poll error");

    } else if (ret == 0) {

        printf("Timeout occurred!\n");

    } else {

        if (fds[0].revents & POLLIN) {

            char buffer[1024];

            fgets(buffer, sizeof(buffer), stdin);

            printf("Read from stdin: %s", buffer);

        }

        if (fds[1].revents & POLLOUT) {

            printf("Standard output is writable.\n");

        }

    }



    return 0;

}

在这个例子中,程序使用 poll 函数同时监视标准输入和标准输出,当标准输入可读或标准输出可写时进行相应处理,如果在 5 秒内没有事件发生,则输出“Timeout occurred!”。

3.epoll

在计算机编程中, epoll 是 Linux 下一种高效的 I/O 多路复用机制。

一、作用

 epoll 可以同时监视大量的文件描述符,当某个文件描述符上有事件发生时,能够快速地通知应用程序进行处理,相比传统的 select 和 poll 函数,在处理大量连接时性能更高。

二、函数及相关数据结构

1.  epoll_create :创建一个 epoll 实例。

2.  epoll_ctl :用于向 epoll 实例中添加、修改或删除要监视的文件描述符及其事件。

3.  epoll_wait :等待 epoll 实例上的事件发生。

涉及的数据结构主要是 epoll_event ,用于描述文件描述符上的事件类型。

三、使用场景

常用于高并发的网络服务器程序,如 Web 服务器、即时通讯服务器等,能够高效地处理大量的客户端连接。

四、示例用法

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/epoll.h>



#define MAX_EVENTS 10



int main() {

    int epoll_fd = epoll_create1(0);

    if (epoll_fd == -1) {

        perror("epoll_create1");

        return 1;

    }



    struct epoll_event event;

    event.events = EPOLLIN;

    event.data.fd = STDIN_FILENO;

    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &event) == -1) {

        perror("epoll_ctl");

        close(epoll_fd);

        return 1;

    }



    struct epoll_event events[MAX_EVENTS];

    while (1) {

        int num_events = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);

        if (num_events == -1) {

            perror("epoll_wait");

            break;

        }



        for (int i = 0; i < num_events; i++) {

            if (events[i].data.fd == STDIN_FILENO) {

                char buffer[1024];

                ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer));

                if (bytes_read <= 0) {

                    perror("read");

                    close(epoll_fd);

                    return 1;

                }

                buffer[bytes_read] = '\0';

                printf("Read from stdin: %s", buffer);

            }

        }

    }



    close(epoll_fd);

    return 0;

}

在这个例子中,程序使用 epoll 监视标准输入,当有数据可读时进行读取并输出。

4.select poll epoll的区别


1. select
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);

select的调用一般要注意几点:
① readfds等是指针结果参数,会被函数修改,所以一般会另外定义一个allread_fdset,保持全部要监听读的句柄,将它的拷贝传递给select函数,返回可读的句柄集合,类型fdset支持赋值运算符=;
② 要注意计算nfds,当新增监听句柄时比较容易修改,当减少监听句柄时较麻烦些,如果要精确修改需要遍历或者采用最大堆等数据结构维护这个句柄集,以方便的找到第二大的句柄,或者干脆在减少监听句柄时不管nfds;
③ timeout如果为NULL表示阻塞等,如果timeout指向的时间为0,表示非阻塞,否则表示select的超时时间;
④ select返回-1表示错误,返回0表示超时时间到没有监听到的事件发生,返回正数表示监听到的所有事件数(包括可读,可 写,异常),通常在处理事件时 会利用这个返回值来提高效率,避免不必要的事件触发检查。(比如总共只有一个事件,已经在可读集合中处理了一个事件,则可写和异常就没必要再去遍历句柄集 判断是否发生事件了);
⑤ Linux的实现中select返回时会将timeout修改为剩余时间,所以重复使用timeout需要注意。

select的缺点在于:
① 由于描述符集合set的限制,每个set最多只能监听FD_SETSIZE(在Linux上是1024)个句柄(不同机器可能不一样);
② 返回的可读集合是个fdset类型,需要对所有的监听读句柄一一进行FD_ISSET的测试来判断是否可读;
③ nfds的存在就是为了解决select的效率问题(select遍历nfds个文件描述符,判断每个描述符是否是自己关心的,对关心的描述符判断是否发生事件)。但是解决不彻底,比如如果只监听0和1000两个句柄,select需要遍历1001个句柄来检查事件。


3. epoll
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

epoll 解决了select和poll的几个性能上的缺陷:①不限制监听的描述符个数(poll也是),只受进程打开描述符总数的限制;②监听性能不随着监听描述 符数的增加而增加,是O(1)的,不再是轮询描述符来探测事件,而是由描述符主动上报事件;③使用共享内存的方式,不在用户和内核之间反复传递监听的描述 符信息;④返回参数中就是触发事件的列表,不用再遍历输入事件表查询各个事件是否被触发。
epoll显著提高性能的前提是:监听大量描述符,并且每次触发事件的描述符文件非常少。
epoll的另外区别是:①epoll创建了描述符,记得close;②支持水平触发和边沿触发。
 

标签:set,IO,多路复用,epoll,int,描述符,fd,第三十一,include
From: https://blog.csdn.net/qq_64792908/article/details/141820053

相关文章

  • 谈谈springboot中@Conditional相关注解
    @Conditional是一个元注解 @ConditionalOnClass(xx.class)这是用于修饰一个类的注解。它主要是让你的代码具有兼容性,如在多模块下common模块中有一些仅仅是部分其他模块依赖、需要配置的类(例如rabbitMQ配置类,我相信他不应该被全模块需要,但是他放在常用模块中依旧是最合适的)只......
  • Spring扩展点系列-InstantiationAwareBeanPostProcessor
    文章目录简介测试一1、配置文件Bean注册2、单元测试方法3、测试类4、输出结果结论测试二1、测试类2、输出结果结论源码解析postProcessPropertiesCommonAnnotationBeanPostProcessorAnnotationInjectedBeanPostProcessor总结简介spring容器中Bean的生命周期内所......
  • Syncfusion Essential Studio 26.2.4
    SyncfusionEssentialStudioisacompletesuitewith1,800+UIcomponentsandframeworksthatcanbeusedforallyourdesktop,web,andmobileapplicationdevelopmentneeds.EssentialStudioconsistsof.NETlibrariesandUIcontrolsthatprovidecomplet......
  • CF1998E2 Eliminating Balls With Merging (Hard Version)
    原题链接考虑对于每个\(i\),算出向左扩展到\(1\)时向右至少和至多扩展到哪里,记为\(minr\)和\(maxr\)。那么也就是说每个\(i\)会对\(minr\simmaxr\)做出贡献,差分一下就可以了。重点是怎么计算这两个东西。先说\(maxr\)。如果暴力跳,过程是:先向左扩展直到不能扩展,然后......
  • 这份 Stable diffusion 提示词攻略,记得收好了!
    大家好,今天给大家分享的是关于Stablediffusion提示词的基础和进阶语法,为什么有必要了解这个呢?首先是提示词对出图效果影响很大,了解提示词的用法有助于我们写出更优秀的提示词;其次就是我们可能会去C站拷贝别人的提示词,虽然直接拷贝过来也能得到不错的效果,但这样只会“知其......
  • JAVA-IO流之字节的输入输出流
    一、IO流的分流按流的流向分为:输入流、输出流根据处理的数据类型分为:字节流、字符流在计算机中、将硬盘上的文件向内存中的流为输入流(读取)、将内存中的流输出到硬盘为输出流(写)二、java流-字节输入输出流概念:流可以理解为一个数据序列、输入流表示从一个源读取数据,输出流表......
  • ffmpeg错误Invalid audio stream. Exactly one MP3 audio stream is required. Could
    错误消息Invalidaudiostream.ExactlyoneMP3audiostreamisrequired.Couldnotwriteheaderforoutputfile#0(incorrectcodecparameters?):InvalidargumentErrorinitializingoutputstream0:0--OnlyAACstreamscanbemuxedbytheADTSmuxerCoul......
  • 《黑神话:悟空》硬件兼容性问题全攻略:从BIOS更新到驱动安装的详细修复指南
    《黑神话:悟空》作为一款备受期待的国产3A大作,自发布以来,确实遇到了一些玩家的电脑硬件兼容性问题。今天小编搜罗了网上反馈一些经常出现的BUG,来教大家如何进行修复操作!更新主板BIOS:对于13/14代酷睿处理器用户,首先尝试更新主板BIOS至最新版本,确保微代码版本为0x129。如果更新......
  • 艾体宝干货丨如何使用IOTA进行远程流量数据采集分析​
    一、问题描述在许多情况下,网络故障不会直接发生在有专业人员进行网络分析的地方。同时,前往偏远地区既费时又费钱。这意味着网络故障排除人员必须能够进行远程记录,最好还能进行远程分析。然而,这也给网络技术带来了挑战,因为传统的VPN技术需要输入防火墙规则,必要时还需要端口转发。此......
  • Spring框架之IOC介绍
    Spring之IOC简介首先,官网中有这样一句话:SpringFrameworkimplementationoftheInversionofControl(IoC)principle.这句话翻译过来就是:Spring实现控制反转(IOC)原理,由此可以得出,InversionofControl(IOC)是一个名为控制反转的原理,而Spring实现了他。而实现这个原理或者说设......