信号驱动IO
信号驱动IO
1. 信号驱动IO概述
信号驱动IO是一种异步IO模型,当文件描述符就绪时,内核通过信号通知应用程序,而不是让应用程序阻塞等待。
1.1 工作原理
- 应用程序启用文件描述符的信号驱动IO
- 应用程序继续执行其他任务(不阻塞)
- 当IO就绪时,内核发送SIGIO信号
- 信号处理函数执行实际的IO操作
1.2 适用场景
- 需要异步处理IO的应用程序
- 需要同时处理多个IO源
- 实时性要求较高的应用
- 不适合高性能服务器(信号处理开销较大)
2. 信号驱动IO设置
2.1 基本设置步骤
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
static int fd;
// SIGIO信号处理函数
void sigio_handler(int sig) {
    char buf[256];
    ssize_t n;
    
    printf("SIGIO received, fd is ready for IO\n");
    
    // 读取数据
    n = read(fd, buf, sizeof(buf) - 1);
    if (n > 0) {
        buf[n] = '\0';
        printf("Read %zd bytes: %s\n", n, buf);
    } else if (n == 0) {
        printf("End of file\n");
    } else {
        if (errno != EAGAIN && errno != EWOULDBLOCK) {
            perror("read");
        }
    }
}
int setup_sigio(int file_fd) {
    struct sigaction sa;
    int flags;
    
    fd = file_fd;
    
    // 设置信号处理函数
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = sigio_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0; // 不要使用SA_RESTART
    
    if (sigaction(SIGIO, &sa, NULL) == -1) {
        perror("sigaction");
        return -1;
    }
    
    // 设置文件描述符的属主,以便接收信号
    if (fcntl(fd, F_SETOWN, getpid()) == -1) {
        perror("fcntl F_SETOWN");
        return -1;
    }
    
    // 获取当前文件状态标志
    flags = fcntl(fd, F_GETFL);
    if (flags == -1) {
        perror("fcntl F_GETFL");
        return -1;
    }
    
    // 启用异步IO和non-blocking
    if (fcntl(fd, F_SETFL, flags | O_ASYNC | O_NONBLOCK) == -1) {
        perror("fcntl F_SETFL");
        return -1;
    }
    
    printf("Signal-driven IO setup complete for fd %d\n", fd);
    return 0;
}
2.2 完整示例:标准输入监控
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
static volatile sig_atomic_t got_signal = 0;
static int signal_fd;
void sigio_handler(int sig) {
    got_signal = 1;
    signal_fd = -1; // 在实际应用中需要确定是哪个fd
}
int main() {
    struct sigaction sa;
    int flags;
    int count = 0;
    
    printf("Signal-driven IO demo for stdin\n");
    printf("Process PID: %d\n", getpid());
    
    // 设置信号处理函数
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = sigio_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    
    if (sigaction(SIGIO, &sa, NULL) == -1) {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }
    
    // 设置标准输入的信号驱动IO
    if (fcntl(STDIN_FILENO, F_SETOWN, getpid()) == -1) {
        perror("fcntl F_SETOWN");
        exit(EXIT_FAILURE);
    }
    
    flags = fcntl(STDIN_FILENO, F_GETFL);
    if (flags == -1) {
        perror("fcntl F_GETFL");
        exit(EXIT_FAILURE);
    }
    
    if (fcntl(STDIN_FILENO, F_SETFL, flags | O_ASYNC | O_NONBLOCK) == -1) {
        perror("fcntl F_SETFL");
        exit(EXIT_FAILURE);
    }
    
    printf("Signal-driven IO is set up. Type something (or 'quit' to exit):\n");
    
    while (1) {
        // 主循环可以做其他工作
        printf("Working... (%d)\n", count++);
        sleep(2);
        
        // 检查是否有信号到达
        if (got_signal) {
            char buf[256];
            ssize_t n;
            
            printf("Processing SIGIO...\n");
            
            // 读取所有可用数据
            while ((n = read(STDIN_FILENO, buf, sizeof(buf) - 1)) > 0) {
                buf[n] = '\0';
                printf("Read: %s", buf);
                
                // 检查退出条件
                if (strncmp(buf, "quit", 4) == 0) {
                    printf("Exiting...\n");
                    exit(EXIT_SUCCESS);
                }
            }
            
            if (n == -1) {
                if (errno != EAGAIN && errno != EWOULDBLOCK) {
                    perror("read");
                }
            }
            
            got_signal = 0;
        }
    }
    
    return 0;
}
3. 高级用法:多文件描述符处理
3.1 使用实时信号
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#define MAX_FDS 10
typedef struct {
    int fd;
    char name[32];
} fd_info_t;
static fd_info_t fd_list[MAX_FDS];
static int fd_count = 0;
// 使用实时信号可以携带更多信息
void sigrtmin_handler(int sig, siginfo_t *info, void *context) {
    int fd = info->si_fd;  // 触发信号的文件描述符
    
    printf("Real-time signal received for fd: %d\n", fd);
    
    // 查找对应的文件描述符信息
    for (int i = 0; i < fd_count; i++) {
        if (fd_list[i].fd == fd) {
            printf("IO ready on %s (fd=%d)\n", fd_list[i].name, fd);
            break;
        }
    }
}
int setup_sigio_rt(int fd, const char *name) {
    struct sigaction sa;
    int flags;
    
    if (fd_count >= MAX_FDS) {
        fprintf(stderr, "Too many file descriptors\n");
        return -1;
    }
    
    // 存储文件描述符信息
    fd_list[fd_count].fd = fd;
    strncpy(fd_list[fd_count].name, name, sizeof(fd_list[fd_count].name) - 1);
    fd_count++;
    
    // 设置实时信号处理(第一次调用时设置)
    if (fd_count == 1) {
        memset(&sa, 0, sizeof(sa));
        sa.sa_sigaction = sigrtmin_handler;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = SA_SIGINFO;  // 使用sa_sigaction
        
        if (sigaction(SIGRTMIN, &sa, NULL) == -1) {
            perror("sigaction SIGRTMIN");
            return -1;
        }
    }
    
    // 设置文件描述符属性
    if (fcntl(fd, F_SETSIG, SIGRTMIN) == -1) {
        perror("fcntl F_SETSIG");
        return -1;
    }
    
    if (fcntl(fd, F_SETOWN, getpid()) == -1) {
        perror("fcntl F_SETOWN");
        return -1;
    }
    
    flags = fcntl(fd, F_GETFL);
    if (flags == -1) {
        perror("fcntl F_GETFL");
        return -1;
    }
    
    if (fcntl(fd, F_SETFL, flags | O_ASYNC | O_NONBLOCK) == -1) {
        perror("fcntl F_SETFL");
        return -1;
    }
    
    printf("Real-time signal IO setup for %s (fd=%d)\n", name, fd);
    return 0;
}
3.2 套接字信号驱动IO
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUFFER_SIZE 1024
static int server_fd;
static volatile sig_atomic_t data_available = 0;
void sigio_handler(int sig) {
    data_available = 1;
}
void setup_socket_sigio(int sock_fd) {
    struct sigaction sa;
    int flags;
    
    // 设置信号处理
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = sigio_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    
    if (sigaction(SIGIO, &sa, NULL) == -1) {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }
    
    // 设置套接字属性
    if (fcntl(sock_fd, F_SETOWN, getpid()) == -1) {
        perror("fcntl F_SETOWN");
        exit(EXIT_FAILURE);
    }
    
    flags = fcntl(sock_fd, F_GETFL);
    if (flags == -1) {
        perror("fcntl F_GETFL");
        exit(EXIT_FAILURE);
    }
    
    if (fcntl(sock_fd, F_SETFL, flags | O_ASYNC | O_NONBLOCK) == -1) {
        perror("fcntl F_SETFL");
        exit(EXIT_FAILURE);
    }
}
int main() {
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    int opt = 1;
    
    // 创建服务器套接字
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }
    
    // 设置套接字选项
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, 
                   &opt, sizeof(opt))) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }
    
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);
    
    // 绑定和监听
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }
    
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }
    
    printf("Server listening on port %d\n", PORT);
    printf("Process PID: %d\n", getpid());
    
    // 设置信号驱动IO
    setup_socket_sigio(server_fd);
    
    while (1) {
        // 主循环可以处理其他任务
        printf("Server is doing other work...\n");
        sleep(3);
        
        if (data_available) {
            printf("Handling incoming connection...\n");
            
            int client_fd;
            struct sockaddr_in client_addr;
            socklen_t client_len = sizeof(client_addr);
            char buffer[BUFFER_SIZE];
            
            // 接受连接
            client_fd = accept(server_fd, (struct sockaddr *)&client_addr, 
                             &client_len);
            if (client_fd >= 0) {
                char client_ip[INET_ADDRSTRLEN];
                inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
                printf("Accepted connection from %s:%d\n", 
                       client_ip, ntohs(client_addr.sin_port));
                
                // 读取客户端数据
                ssize_t n = read(client_fd, buffer, sizeof(buffer) - 1);
                if (n > 0) {
                    buffer[n] = '\0';
                    printf("Received: %s\n", buffer);
                    
                    // 发送响应
                    const char *response = "Hello from signal-driven server!\n";
                    write(client_fd, response, strlen(response));
                }
                
                close(client_fd);
                printf("Connection closed\n");
            } else {
                if (errno != EAGAIN && errno != EWOULDBLOCK) {
                    perror("accept");
                }
            }
            
            data_available = 0;
        }
    }
    
    close(server_fd);
    return 0;
}
4. 信号驱动IO的限制和问题
4.1 常见问题及解决方案
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
// 问题1:信号丢失
// 解决方案:使用信号队列或实时信号
// 问题2:多个文件描述符的信号区分
// 解决方案1:在信号处理函数中检查所有fd
void sigio_handler_check_all(int sig) {
    printf("SIGIO received, checking all monitored fds...\n");
    
    // 这里需要维护一个被监控的fd列表
    // 检查每个fd的IO状态
}
// 解决方案2:使用不同的信号
void setup_multiple_signals() {
    struct sigaction sa;
    
    // 为不同的fd设置不同的信号
    // 但这受限于可用信号数量
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = sigio_handler_check_all;
    sigemptyset(&sa.sa_mask);
    
    // 可以绑定多个信号到同一个处理函数
    sigaction(SIGIO, &sa, NULL);
    sigaction(SIGURG, &sa, NULL);  // 用于带外数据
}
// 问题3:可移植性
// 不同Unix系统对信号驱动IO的支持不同
void check_system_support() {
#ifdef O_ASYNC
    printf("O_ASYNC is supported\n");
#else
    printf("O_ASYNC is NOT supported\n");
#endif
#ifdef F_SETOWN
    printf("F_SETOWN is supported\n");
#else
    printf("F_SETOWN is NOT supported\n");
#endif
}
// 问题4:性能考虑
// 信号处理的开销可能比轮询更大
void performance_considerations() {
    printf("Signal-driven IO performance notes:\n");
    printf("1. Signal handling has overhead\n");
    printf("2. Not suitable for high-rate IO\n");
    printf("3. Good for low-rate, responsive applications\n");
    printf("4. Consider epoll for high-performance servers\n");
}
5. 与其他IO模型对比
5.1 性能测试示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/time.h>
#include <string.h>
#define TEST_DURATION 5  // 测试5秒
static volatile int signal_count = 0;
static struct timeval start_time, end_time;
void test_sigio_handler(int sig) {
    signal_count++;
}
// 信号驱动IO性能测试
void test_signal_driven_io() {
    struct sigaction sa;
    int pipefd[2];
    int flags;
    char buffer[1024];
    
    // 创建管道用于测试
    if (pipe(pipefd) == -1) {
        perror("pipe");
        return;
    }
    
    // 设置信号处理
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = test_sigio_handler;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGIO, &sa, NULL);
    
    // 设置写端为信号驱动
    flags = fcntl(pipefd[1], F_GETFL);
    fcntl(pipefd[1], F_SETFL, flags | O_ASYNC);
    fcntl(pipefd[1], F_SETOWN, getpid());
    
    printf("Starting signal-driven IO performance test...\n");
    
    gettimeofday(&start_time, NULL);
    signal_count = 0;
    
    // 持续写入数据触发信号
    int writes = 0;
    while (1) {
        struct timeval current_time;
        gettimeofday(¤t_time, NULL);
        
        if (current_time.tv_sec - start_time.tv_sec >= TEST_DURATION) {
            break;
        }
        
        // 写入数据触发SIGIO
        if (write(pipefd[1], buffer, sizeof(buffer)) > 0) {
            writes++;
        }
        
        // 从读端读取数据,避免管道满
        read(pipefd[0], buffer, sizeof(buffer));
    }
    
    gettimeofday(&end_time, NULL);
    
    double elapsed = (end_time.tv_sec - start_time.tv_sec) + 
                    (end_time.tv_usec - start_time.tv_usec) / 1000000.0;
    
    printf("Signal-driven IO test results:\n");
    printf("  Duration: %.2f seconds\n", elapsed);
    printf("  Signals received: %d\n", signal_count);
    printf("  Writes performed: %d\n", writes);
    printf("  Signals per second: %.2f\n", signal_count / elapsed);
    
    close(pipefd[0]);
    close(pipefd[1]);
}
6. 最佳实践
6.1 使用准则
// 1. 总是使用非阻塞IO配合信号驱动IO
flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | O_ASYNC | O_NONBLOCK);
// 2. 在信号处理函数中做最少的工作
void efficient_handler(int sig) {
    // 只设置标志,在主循环中处理IO
    io_ready_flag = 1;
}
// 3. 使用实时信号避免信号丢失
fcntl(fd, F_SETSIG, SIGRTMIN);  // 使用实时信号
// 4. 正确处理EAGAIN/EWOULDBLOCK
ssize_t n = read(fd, buf, size);
if (n == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
    // 没有数据可读,正常情况
}
// 5. 考虑信号队列限制
printf("Signal queue length: %ld\n", sysconf(_SC_SIGQUEUE_MAX));
6.2 错误处理
int robust_sigio_setup(int fd) {
    struct sigaction sa;
    int flags;
    
    // 检查文件描述符有效性
    if (fcntl(fd, F_GETFD) == -1) {
        perror("Invalid file descriptor");
        return -1;
    }
    
    // 设置信号处理
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = sigio_handler;
    sigemptyset(&sa.sa_mask);
    
    if (sigaction(SIGIO, &sa, NULL) == -1) {
        perror("sigaction failed");
        return -1;
    }
    
    // 设置文件属主
    if (fcntl(fd, F_SETOWN, getpid()) == -1) {
        perror("fcntl F_SETOWN failed");
        return -1;
    }
    
    // 启用异步IO
    flags = fcntl(fd, F_GETFL);
    if (flags == -1) {
        perror("fcntl F_GETFL failed");
        return -1;
    }
    
    if (fcntl(fd, F_SETFL, flags | O_ASYNC | O_NONBLOCK) == -1) {
        perror("fcntl F_SETFL failed");
        return -1;
    }
    
    return 0;
}
信号驱动IO提供了一种异步处理IO的方式,但在高性能场景下通常不如epoll高效。它适用于需要快速响应但IO频率不高的应用程序。
            本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 恒星不见
        
     评论
            
                匿名评论
                隐私政策
            
            
                你无需删除空行,直接评论以获取最佳展示效果
            
         
            
        
