epoll系列系统调用

内核事件表
epoll是Linux特有的I/O复用函数。使用一组函数来完成任务。epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,从而无须像select和poll那样每次调用都要重复传入文件描述符或事件集。但epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表。
#include<sys/epoll.h>
int epoll_create(int size)
size参数现在并不起作用,只是给内核一个提示,告诉它事件表需要多大。该函数返回的文件描述符将用作其他所有epoll系统调用的第一个参数,以指定要访问的内核
事件表。
#include<sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
fd参数是要操作的文件描述符,op参数则指定操作类型。
操作类型有如下3种:
EPOLL_CTL_ADD,EPOLL_CTL_MOD,EPOLL_CTL_DEL
event参数指定事件,它是epoll_event结构指针类型。epoll_event的定义如下:
struct epoll_event
{
  __uint32_t events;/*epoll事件*/
  epoll_data_t data;/*用户数据*/
};

其中events成员描述事件类型。epoll支持的事件类型和poll基本相同。
表示epoll事件类型的宏是在poll对应的宏上加上E,比如epoll的数据可读事件是EPOLLIN,但epoll有两个额外的事件类型---EPOLLET和EPOLLONESHOT。它们对于epoll的高校运作非常关键,。
typedef union epoll_data_t
{
   void* ptr;
   int fd;
   uint32_t u32;
   uint64_t u64;
}epoll_data_t;
epoll_data_t 是一个联合体,其4个成员中使用最多的是fd,它指定事件所从属的目标文件描述符。ptr成员可用来指定与fd相关的用户数据。但由于epoll_data_t是一个联合体。

epoll_wait函数
epoll系列系统调用的主要接口是epoll_wait函数。
#include<sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event* events,int maxevents,
             int timeout);
epoll_wait函数如果检测到事件,就将所有就绪的时间从内核事件表中复制到它的第二个参数events指向的数组中。这个数组只用于输出epoll_wait检测到的就绪事件,而不像select和poll的数组参数那样既用于传入用户注册的事件,又用于输出内核检测到的就绪事件。
int ret = poll(fds,MAX_EVENT_NUMBER,-1);
for(int i = 0; i < MAX_EVENT_NUMBER; i++)
{
   if(fds[i].revents & POLLIN)
   {
       int sockfd = fds[i].fd;
   }
};

int ret =  epoll_wait(epollfd,events,MAX_EVENT_NUMBER,-1);
for(int i = 0; i < ret; i++)
{
   int sockfd = events[i].data.fd;
}

LT和ET模式
epoll对文件描述符的操作有两种模式:LT模式和ET模式。LT模式是默认的工作模式,这种模式下epoll相当于一个效率较高的poll。当往epoll内核事件中注册一个文件描述符上的EPOLLET事件时,epoll将以ET模式来操作该文件描述符。ET模式是epoll的高校工作模式。对于采用LT工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。这样,当应用程序下一次调用epoll_wait时,epoll_wait还会再次向应用程序通告此事件,直到该事件被处理。而对于采用ET工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait调用将不再通知这一事件。可见,ET模式在很大程度上降低了同一个epoll是事件被重复触发的次数。因为效率要比LT模式高。
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <pthread.h>

#define MAX_EVENT_NUMBER 1024
#define BUFFER_SIZE 10

int setnonblocking( int fd )
{
    int old_option = fcntl( fd, F_GETFL );
    int new_option = old_option | O_NONBLOCK;
    fcntl( fd, F_SETFL, new_option );
    return old_option;
}

void addfd( int epollfd, int fd, bool enable_et )
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN;
    if( enable_et )
    {
        event.events |= EPOLLET;
    }
    epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );
    setnonblocking( fd );
}

void lt( epoll_event* events, int number, int epollfd, int listenfd )
{
    char buf[ BUFFER_SIZE ];
    for ( int i = 0; i < number; i++ )
    {
        int sockfd = events[i].data.fd;
        if ( sockfd == listenfd )
        {
            struct sockaddr_in client_address;
            socklen_t client_addrlength = sizeof( client_address );
            int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
            addfd( epollfd, connfd, false );
        }
        else if ( events[i].events & EPOLLIN )
        {
            printf( "event trigger once\n" );
            memset( buf, '\0', BUFFER_SIZE );
            int ret = recv( sockfd, buf, BUFFER_SIZE-1, 0 );
            if( ret <= 0 )
            {
                close( sockfd );
                continue;
            }
            printf( "get %d bytes of content: %s\n", ret, buf );
        }
        else
        {
            printf( "something else happened \n" );
        }
    }
}

void et( epoll_event* events, int number, int epollfd, int listenfd )
{
    char buf[ BUFFER_SIZE ];
    for ( int i = 0; i < number; i++ )
    {
        int sockfd = events[i].data.fd;
        if ( sockfd == listenfd )
        {
            struct sockaddr_in client_address;
            socklen_t client_addrlength = sizeof( client_address );
            int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
            addfd( epollfd, connfd, true );
        }
        else if ( events[i].events & EPOLLIN )
        {
            printf( "event trigger once\n" );
            while( 1 )
            {
                memset( buf, '\0', BUFFER_SIZE );
                int ret = recv( sockfd, buf, BUFFER_SIZE-1, 0 );
                if( ret < 0 )
                {
                    if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) )
                    {
                        printf( "read later\n" );
                        break;
                    }
                    close( sockfd );
                    break;
                }
                else if( ret == 0 )
                {
                    close( sockfd );
                }
                else
                {
                    printf( "get %d bytes of content: %s\n", ret, buf );
                }
            }
        }
        else
        {
            printf( "something else happened \n" );
        }
    }
}

int main( int argc, char* argv[] )
{
    if( argc <= 2 )
    {
        printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi( argv[2] );

    int ret = 0;
    struct sockaddr_in address;
    bzero( &address, sizeof( address ) );
    address.sin_family = AF_INET;
    inet_pton( AF_INET, ip, &address.sin_addr );
    address.sin_port = htons( port );

    int listenfd = socket( PF_INET, SOCK_STREAM, 0 );
    assert( listenfd >= 0 );

    ret = bind( listenfd, ( struct sockaddr* )&address, sizeof( address ) );
    assert( ret != -1 );

    ret = listen( listenfd, 5 );
    assert( ret != -1 );

    epoll_event events[ MAX_EVENT_NUMBER ];
    int epollfd = epoll_create( 5 );
    assert( epollfd != -1 );
    addfd( epollfd, listenfd, true );

    while( 1 )
    {
        int ret = epoll_wait( epollfd, events, MAX_EVENT_NUMBER, -1 );
        if ( ret < 0 )
        {
            printf( "epoll failure\n" );
            break;
        }
    
        lt( events, ret, epollfd, listenfd );
        //et( events, ret, epollfd, listenfd );
    }

    close( listenfd );
    return 0;
}


EPOLLONESHOT事件
克服了在并发程序中出现两个线程同时操作一个socket的局面。当一个线程操作某一个socket时,其他线程是不可能有机会操作该socket的。但反过来思考,注册了POLLONESHOT事件的socket一旦被某个线程处理完毕,该线程就应该立即重置这个socket上的EPOLLONESHOT事件,以确保这个socket下一次可读时,其EPOLLIN事件能被触发,进而让其他线程有机会处理这个socket.
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <pthread.h>

#define MAX_EVENT_NUMBER 1024
#define BUFFER_SIZE 1024
struct fds
{
   int epollfd;
   int sockfd;
};

int setnonblocking( int fd )
{
    int old_option = fcntl( fd, F_GETFL );
    int new_option = old_option | O_NONBLOCK;
    fcntl( fd, F_SETFL, new_option );
    return old_option;
}

void addfd( int epollfd, int fd, bool oneshot )
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET;
    if( oneshot )
    {
        event.events |= EPOLLONESHOT;
    }
    epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );
    setnonblocking( fd );
}

void reset_oneshot( int epollfd, int fd )
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
    epoll_ctl( epollfd, EPOLL_CTL_MOD, fd, &event );


void* worker( void* arg )
{
    int sockfd = ( (fds*)arg )->sockfd;
    int epollfd = ( (fds*)arg )->epollfd;
    printf( "start new thread to receive data on fd: %d\n", sockfd );
    char buf[ BUFFER_SIZE ];
    memset( buf, '\0', BUFFER_SIZE );
    while( 1 )
    {
        int ret = recv( sockfd, buf, BUFFER_SIZE-1, 0 );
        if( ret == 0 )
        {
            close( sockfd );
            printf( "foreiner closed the connection\n" );
            break;
        }
        else if( ret < 0 )
        {
            if( errno == EAGAIN )
            {
                reset_oneshot( epollfd, sockfd );
                printf( "read later\n" );
                break;
            }
        }
        else
        {
            printf( "get content: %s\n", buf );
            sleep( 5 );
        }
    }
    printf( "end thread receiving data on fd: %d\n", sockfd );
}

int main( int argc, char* argv[] )
{
    if( argc <= 2 )
    {
        printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi( argv[2] );

    int ret = 0;
    struct sockaddr_in address;
    bzero( &address, sizeof( address ) );
    address.sin_family = AF_INET;
    inet_pton( AF_INET, ip, &address.sin_addr );
    address.sin_port = htons( port );

    int listenfd = socket( PF_INET, SOCK_STREAM, 0 );
    assert( listenfd >= 0 );

    ret = bind( listenfd, ( struct sockaddr* )&address, sizeof( address ) );
    assert( ret != -1 );

    ret = listen( listenfd, 5 );
    assert( ret != -1 );

    epoll_event events[ MAX_EVENT_NUMBER ];
    int epollfd = epoll_create( 5 );
    assert( epollfd != -1 );
    addfd( epollfd, listenfd, false );


    while( 1 )
    {
        int ret = epoll_wait( epollfd, events, MAX_EVENT_NUMBER, -1 );
        if ( ret < 0 )
        {
            printf( "epoll failure\n" );
            break;
        }
    
        for ( int i = 0; i < ret; i++ )
        {
            int sockfd = events[i].data.fd;
            if ( sockfd == listenfd )
            {
                struct sockaddr_in client_address;
                socklen_t client_addrlength = sizeof( client_address );
                int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
                addfd( epollfd, connfd, true );
            }
            else if ( events[i].events & EPOLLIN )
            {
                pthread_t thread;
                fds fds_for_new_worker;
                fds_for_new_worker.epollfd = epollfd;
                fds_for_new_worker.sockfd = sockfd;
                pthread_create( &thread, NULL, worker, ( void* )&fds_for_new_worker );
            }
            else
            {
                printf( "something else happened \n" );
            }
        }
    }

    close( listenfd );
    return 0;
}


 

<think>嗯,用户问的是Linux的实时信号会打断哪些系统调用。这个问题我需要仔细回忆一下之前学过的知识,可能还要查证一些资料确保准确。首先,我记得Linux的信号分为标准信号和实时信号,实时信号是从SIGRTMIN到SIGRTMAX的那些。实时信号相比标准信号有什么不同呢?比如排队机制,多个实时信号会按顺序传递,不会被合并。而标准信号如果多次发送可能会被合并成一个。 然后关于系统调用被打断的情况。系统调用被信号打断通常是因为信号处理函数被设置,当进程正在执行一个系统调用时,如果收到信号,系统调用可能会被中断,返回EINTR错误。不过,这取决于系统调用是否被信号处理函数设置为自动重启,或者是否使用了SA_RESTART标志。但是实时信号的行为是否有所不同呢? 我记得实时信号在某些情况下可能会影响不同的系统调用。比如,一些慢速系统调用(如read、write、wait等)在被信号中断后会返回EINTR,而实时信号可能会更倾向于打断这些调用。但需要具体确定哪些系统调用会被实时信号打断。另外,可能有些系统调用即使被信号中断也不会返回EINTR,比如某些阻塞在不可中断状态的操作,比如等待磁盘I/O。 另外,用户可能还关心实时信号和标准信号在打断系统调用方面有什么不同。比如,是否实时信号更有可能导致系统调用中断,或者是否某些系统调用对实时信号特别敏感。此外,可能需要提到SA_RESTART标志的作用,当使用sigaction设置信号处理时,如果指定了SA_RESTART,那么被中断的系统调用会自动重启,而不是返回EINTR。但实时信号是否默认带有这个标志,或者是否行为不同? 还有,可能需要举例说明哪些常见的系统调用会被实时信号打断,比如accept、read、write、sleep类的函数,或者等待子进程的wait系列函数。同时,像poll、select、epoll这样的I/O多路复用函数是否也会被实时信号中断? 此外,用户可能想知道如何处理被中断的系统调用,比如检查errno是否为EINTR,然后决定是否重新调用。这在实际编程中是一个常见的问题,需要正确处理以避免意外错误。 可能还需要提到,某些系统调用即使被信号打断,也会自动恢复,特别是当使用SA_RESTART标志时。但对于实时信号,是否默认行为不同?比如,标准信号可能默认有SA_RESTART,而实时信号可能没有?或者是否相反? 总结来说,回答需要包括以下几点: 1. 实时信号与标准信号的不同之处,特别是排队机制。 2. 系统调用被信号中断的一般情况,即返回EINTR。 3. 哪些系统调用会被实时信号打断,列举常见的例子。 4. SA_RESTART标志的作用及其对实时信号的影响。 5. 如何处理被中断的系统调用,比如重启它们。 需要确认这些信息的准确性,可能需要查阅相关文档或资料。比如,APUE(Advanced Programming in the UNIX Environment)中的相关章节,或者Linux的man page,特别是signal(7)、sigaction(2)等部分。</think>Linux 的实时信号(Real-Time Signals,范围 `SIGRTMIN` 到 `SIGRTMAX`)在打断系统调用时的行为与标准信号类似,但因其支持排队机制(多个信号按顺序传递,不会丢失),可能在某些场景下对系统调用的影响更显著。以下是关键点说明: --- ### **1. 实时信号与标准信号的差异** - **标准信号**(如 `SIGINT`, `SIGTERM`): - 不支持排队,多次发送可能被合并为一次。 - 默认行为可能自动重启被中断的系统调用(依赖 `SA_RESTART` 标志)。 - **实时信号**(如 `SIGRTMIN+1`): - 支持排队,确保每个信号被独立处理。 - 默认不自动重启被中断的系统调用(除非显式设置 `SA_RESTART`)。 --- ### **2. 系统调用被信号打断的通用规则** 当进程执行 **阻塞式系统调用**(如 `read`, `write`, `accept`)时,若被信号中断: - 系统调用立即返回,并设置 `errno` 为 `EINTR`。 - 进程需检查 `EINTR` 并决定是否重试。 **例外**:某些系统调用(如 `sleep`)即使被中断,也可能返回剩余未休眠的时间,而非 `EINTR`。 --- ### **3. 实时信号可能打断的系统调用(常见示例)** 以下类型系统调用可能被实时信号中断(返回 `EINTR`): - **I/O 操作**:`read`, `write`, `recv`, `send`(在阻塞模式下)。 - **进程控制**:`wait`, `waitpid`, `waitid`。 - **网络操作**:`accept`, `connect`, `sendto`, `recvfrom`。 - **同步原语**:`sem_wait`, `pthread_mutex_lock`(某些实现)。 - **定时与休眠**:`nanosleep`, `poll`, `select`, `epoll_wait`。 --- ### **4. `SA_RESTART` 标志的影响** - **若设置 `SA_RESTART`**(通过 `sigaction`): 被中断的系统调用会自动重启,无需手动处理 `EINTR`。 - **实时信号的默认行为**: 默认不启用 `SA_RESTART`,因此更可能返回 `EINTR`。需显式设置该标志以改变行为。 --- ### **5. 处理被中断的系统调用** 代码示例(以 `read` 为例): ```c while (1) { ssize_t n = read(fd, buf, size); if (n >= 0) { // 成功读取数据 break; } else if (errno == EINTR) { // 被信号中断,重新调用 read continue; } else { // 处理其他错误 perror("read"); break; } } ``` --- ### **6. 不可中断的系统调用** 某些系统调用处于 **不可中断状态**(如磁盘 I/O、部分内核操作),即使收到信号也不会返回 `EINTR`。例如: - `write` 在等待磁盘写入时可能无法被中断。 - `uninterruptible sleep` 状态(如某些内核操作)不会被任何信号打断。 --- ### **总结** - **实时信号**更倾向于打断系统调用并返回 `EINTR`(默认无 `SA_RESTART`)。 - **关键处理原则**:对可能返回 `EINTR` 的系统调用,需在代码中检查并决定是否重试。 - **扩展阅读**:可通过 `man 7 signal` 和 `man 2 sigaction` 查阅更详细的 Linux 文档。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值