• 首页
  • 搜索
  • 工具
  • 分类
  • 日志
  • 友链
  • 图片

It's Geek KingYoungy

KEEP CHALLENGE

分类 系统编程基础 下的文章

系统编程基础

C语言原子量的使用

2025-04-18 浏览量 182 暂无评论

在C语言中,原子量(Atomic)用于实现多线程环境下的无锁同步操作,确保对共享变量的操作是不可分割的(即原子性)。C11标准引入了 <stdatomic.h> 头文件,提供了对原子操作的支持。
互斥量用于保护多行代码块,原子量可以保护一行代码(对原子变量的操作)。以下是原子量的核心用法和注意事项:


1. 原子类型声明

使用 _Atomic 修饰符声明原子变量:

#include <stdatomic.h>

_Atomic int counter = ATOMIC_VAR_INIT(0); // 声明并初始化为0

或简写:

atomic_int counter = 0; // 等效于 _Atomic int

2. 基本原子操作

加载(Load)和存储(Store)

int value;
value = atomic_load(&counter);        // 原子读取
atomic_store(&counter, 42);          // 原子写入

交换(Exchange)

int old = atomic_exchange(&counter, 100); // 将counter设为100,返回旧值

比较并交换(CAS, Compare-And-Swap)

int expected = 10;
int desired = 20;
if (atomic_compare_exchange_strong(&counter, &expected, desired)) {
    // 成功:counter原值为expected,被更新为desired
} else {
    // 失败:counter当前值 != expected,expected被更新为实际值
}

3. 原子算术/位运算

atomic_fetch_add(&counter, 5);    // 原子加5,返回旧值
atomic_fetch_sub(&counter, 3);    // 原子减3
atomic_fetch_or(&counter, 0x1);   // 原子按位或
atomic_fetch_and(&counter, ~0x1); // 原子按位与

4. 内存顺序(Memory Order)

指定原子操作的内存同步行为,控制指令重排和可见性:

atomic_store_explicit(&counter, 42, memory_order_release); // 写入释放
int val = atomic_load_explicit(&counter, memory_order_acquire); // 读取获取

常用选项:
• memory_order_relaxed:仅保证原子性,无同步/顺序约束。
• memory_order_acquire:本操作前的所有读写不会被重排到它之后。
• memory_order_release:本操作后的所有读写不会被重排到它之前。
• memory_order_seq_cst:严格顺序一致性(默认)。


5. 示例:线程安全的计数器

#include <stdatomic.h>
#include <stdio.h>
#include <threads.h>

atomic_int counter = 0;

int thread_func(void *arg) {
    for (int i = 0; i < 10000; i++) {
        atomic_fetch_add(&counter, 1);
    }
    return 0;
}

int main() {
    thrd_t t1, t2;
    thrd_create(&t1, thread_func, NULL);
    thrd_create(&t2, thread_func, NULL);
    thrd_join(t1, NULL);
    thrd_join(t2, NULL);
    printf("Counter: %d\n", counter); // 正确输出20000
    return 0;
}

6. 注意事项

  1. 性能:原子操作比普通操作慢,仅在必要时使用。
  2. 适用场景:简单共享变量(如标志、计数器),复杂逻辑仍需互斥锁。
  3. 兼容性:需C11及以上编译器(如GCC -std=c11)。
  4. 避免ABA问题:CAS操作需注意值被其他线程多次修改的情况。

7. 扩展知识

• 无锁编程:原子量是实现无锁数据结构的基础(如队列、栈)。
• 编译器屏障:atomic_signal_fence() 用于线程与信号处理函数间的同步。

通过合理使用原子量,可以高效解决多线程竞争问题,但需谨慎设计以避免逻辑错误。

- 阅读全文 -
系统编程基础

UNIX 98伪终端核心系统调用及函数封装

2025-04-02 浏览量 161 暂无评论
伪终端导图.jpg

UNIX 98伪终端核心系统调用及实践实现

1. UNIX 98伪终端核心系统调用

以下为伪终端的工作模式,伪终端即伪终端对,分为伪终端主设备与伪终端从设备。主设备向用户显示信息或读取输入,从设备与作业进程交互。
驱动程序介于两者之间,维护有输入队列(主设备->从设备)与输出队列(从设备->主设备)。回显指的是将输入队列的数据添加至输出队列,这里的行缓冲(不同于stdio库的行缓冲)指输入队列出现换行符才将输入队列的内容发给从设备供前台进程组读取。
伪终端导图.jpg

1.1 posix_openpt

功能描述

打开伪终端主设备(PTY Master),返回主设备文件描述符。

函数声明

#include <stdlib.h>
#include <fcntl.h>

int posix_openpt(int flags);

参数详解

  • flags:打开模式标志位(按位或组合):

    • O_RDWR:必须指定的读写模式
    • O_NOCTTY:防止成为控制终端
    • O_CLOEXEC:设置close-on-exec标志

返回值

  • 成功:返回主设备文件描述符(≥3)
  • 失败:返回-1,设置errno:

    • ENOMEM:内核内存不足
    • ENOSPC:伪终端资源耗尽
    • EMFILE:进程文件描述符达上限

1.2 grantpt

功能描述

设置从设备(PTY Slave)的权限为可访问。

函数声明

#include <stdlib.h>

int grantpt(int master_fd);

参数详解

  • master_fd:由posix_openpt返回的主设备描述符

返回值

  • 成功:返回0
  • 失败:返回-1,设置errno:

    • EINVAL:无效文件描述符
    • EACCES:权限不足

1.3 unlockpt

功能描述

解除主设备的访问锁,允许从设备被打开。

函数声明

#include <stdlib.h>

int unlockpt(int master_fd);

参数详解

  • master_fd:已通过grantpt授权的主设备描述符

返回值

  • 成功:返回0
  • 失败:返回-1,设置errno:

    • EINVAL:无效文件描述符

1.4 ptsname

功能描述

获取伪终端从设备路径名。

函数声明

#include <stdlib.h>

char *ptsname(int master_fd);

参数详解

  • master_fd:已解锁的主设备描述符

返回值

  • 成功:返回从设备路径字符串指针(如/dev/pts/5)
  • 失败:返回NULL,设置errno:

    • EINVAL:无效文件描述符

2. ptyMasterOpen函数实现

功能目标

打开伪终端主设备并完成初始化配置,返回从设备路径。

函数原型

#include <fcntl.h>
#include <string.h>
#include <errno.h>

int ptyMasterOpen(char *slaveName, size_t snLen) {
    int master_fd;
    char *slave_path;
    
    master_fd = posix_openpt(O_RDWR | O_NOCTTY);
    if (master_fd == -1) return -1;

    if (grantpt(master_fd) == -1) {
        close(master_fd);
        return -1;
    }

    if (unlockpt(master_fd) == -1) {
        close(master_fd);
        return -1;
    }

    slave_path = ptsname(master_fd);
    if (slave_path == NULL) {
        close(master_fd);
        errno = EINVAL;
        return -1;
    }

    if (strlen(slave_path) >= snLen) {
        close(master_fd);
        errno = EOVERFLOW;
        return -1;
    }

    strncpy(slaveName, slave_path, snLen);
    return master_fd;
}

关键处理逻辑

  • 缓冲区验证:通过strlen校验目标缓冲区容量
  • 错误处理链:确保资源泄露防护
  • 标志位组合:O_RDWR|O_NOCTTY保证基本功能

3. ptyFork函数实现

3.1 核心数据结构

termios结构体(终端属性)

struct termios {
    tcflag_t c_iflag;  // 输入模式标志
    tcflag_t c_oflag;  // 输出模式标志
    tcflag_t c_cflag;  // 控制模式标志
    tcflag_t c_lflag;  // 本地模式标志
    cc_t c_cc[NCCS];   // 控制字符数组
};

winsize结构体(窗口尺寸)

struct winsize {
    unsigned short ws_row;    // 行数
    unsigned short ws_col;    // 列数
    unsigned short ws_xpixel; // 水平像素
    unsigned short ws_ypixel; // 垂直像素
};

3.2 函数实现

#include <unistd.h>
#include <sys/ioctl.h>

pid_t ptyFork(int *masterFd, char *slaveName, size_t snLen,
              const struct termios *slaveTermios,
              const struct winsize *slaveWS) {
    int mfd, saved_errno;
    pid_t childPid;

    mfd = ptyMasterOpen(slaveName, snLen);
    if (mfd == -1) return -1;

    childPid = fork();
    if (childPid == -1) {
        saved_errno = errno;
        close(mfd);
        errno = saved_errno;
        return -1;
    }

    if (childPid != 0) {  // 父进程
        *masterFd = mfd;
        return childPid;
    }

    // 子进程执行流
    if (setsid() == -1) _exit(EXIT_FAILURE);

    close(mfd);

    int slave_fd = open(slaveName, O_RDWR);
    if (slave_fd == -1) _exit(EXIT_FAILURE);

#ifdef TIOCSCTTY
    if (ioctl(slave_fd, TIOCSCTTY, 0) == -1)
        _exit(EXIT_FAILURE);
#endif

    if (slaveTermios && tcsetattr(slave_fd, TCSANOW, slaveTermios) == -1)
        _exit(EXIT_FAILURE);

    if (slaveWS && ioctl(slave_fd, TIOCSWINSZ, slaveWS) == -1)
        _exit(EXIT_FAILURE);

    dup2(slave_fd, STDIN_FILENO);
    dup2(slave_fd, STDOUT_FILENO);
    dup2(slave_fd, STDERR_FILENO);

    if (slave_fd > STDERR_FILENO)
        close(slave_fd);

    return 0;
}

3.3 关键操作说明

  1. 会话控制:

    • setsid():创建新会话,脱离原终端控制
    • TIOCSCTTY:显式获取控制终端(BSD兼容)
  2. 描述符重定向:

    • dup2三步操作:实现标准流全重定向
  3. 属性继承:

    • tcsetattr:精确复制原终端行为特征
    • TIOCSWINSZ:保持窗口尺寸一致性

4. 系统调用关系图谱

graph TD
    A[posix_openpt] --> B[grantpt]
    B --> C[unlockpt]
    C --> D[ptsname]
    D --> E[ptyMasterOpen]
    E --> F[fork]
    F --> G[setsid]
    G --> H[ioctl-TIOCSCTTY]
    H --> I[tcsetattr]
    I --> J[dup2]

通过系统调用的组合使用,可构建完整的伪终端通信通道,为高级终端应用开发奠定基础。

- 阅读全文 -
系统编程基础

Linux信号处理深度解析:从基础到高级应用

2025-03-31 浏览量 130 暂无评论

Linux信号处理深度解析:从基础到高级应用

1. 信号发送机制

1.1 kill系统调用

功能:向指定进程或进程组发送信号
函数声明:

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

int kill(pid_t pid, int sig);

参数说明:
• pid:目标进程/进程组标识符
• >0:发送给特定进程
• =0:发送给调用进程所属进程组
• =-1:广播发送给所有有权进程
• <-1:发送给进程组ID为|pid|的组
• sig:信号编号(如SIGTERM=15)

返回值:
• 成功返回0,失败返回-1并设置errno(EPERM/ESRCH/EINVAL)


1.2 killpg系统调用

功能:向指定进程组发送信号
函数声明:

#include <signal.h>

int killpg(pid_t pgrp, int sig);

参数说明:
• pgrp:目标进程组ID(0表示当前进程组)
• sig:同kill系统调用

返回值:
• 同kill系统调用


1.3 raise函数

功能:向当前进程发送信号
函数声明:

#include <signal.h>

int raise(int sig);

参数说明:
• sig:待发送信号编号

返回值:
• 成功返回0,失败返回非0值


2. 信号处理机制

2.1 signal函数

功能:注册标准信号处理函数
函数声明:

#include <signal.h>

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

参数说明:
• signum:待捕获信号编号(如SIGINT)
• handler:处理函数指针,可取值:
• SIG_IGN:忽略信号
• SIG_DFL:默认处理
• 自定义函数指针

返回值:
• 成功返回原处理函数指针,失败返回SIG_ERR


2.2 sigaction系统调用

功能:支持实时信号处理的增强型注册接口
函数声明:

#include <signal.h>

int sigaction(int signum, 
             const struct sigaction *act,
             struct sigaction *oldact);

struct sigaction {
    void     (*sa_handler)(int);
    void     (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t   sa_mask;
    int        sa_flags;
    void     (*sa_restorer)(void);
};

参数说明:
• signum:同signal
• act:新处理配置
• sa_handler:标准处理函数
• sa_sigaction:实时信号处理函数(需设置SA_SIGINFO标志)
• sa_mask:执行处理函数期间阻塞的信号集
• sa_flags:控制标志位,常用值:

◦ `SA_NOCLDSTOP`:子进程停止时不产生SIGCHLD
◦ `SA_RESTART`:自动重启被中断的系统调用
◦ `SA_SIGINFO`:使用sa_sigaction处理函数

• oldact:保存原处理配置

siginfo_t结构体:

typedef struct {
    int      si_signo;    /* 信号编号 */
    int      si_code;     /* 信号来源 */
    pid_t    si_pid;      /* 发送进程PID */
    uid_t    si_uid;      /* 发送进程UID */
    void    *si_addr;     /* 引发故障的内存地址 */
    int      si_status;   /* 子进程退出状态 */
    union sigval si_value;/* 伴随数据 */
} siginfo_t;

返回值:
• 成功返回0,失败返回-1


3. 信号屏蔽控制

3.1 sigprocmask系统调用

功能:修改进程信号屏蔽字
函数声明:

#include <signal.h>

int sigprocmask(int how, 
               const sigset_t *set,
               sigset_t *oldset);

参数说明:
• how:操作类型
• SIG_BLOCK:添加set到屏蔽集
• SIG_UNBLOCK:从屏蔽集移除set
• SIG_SETMASK:直接设置屏蔽集为set
• set:待操作信号集
• oldset:保存原屏蔽集

返回值:
• 成功返回0,失败返回-1


4. 信号等待策略

4.1 sigsuspend系统调用

功能:原子操作解除信号屏蔽并等待信号
函数声明:

#include <signal.h>

int sigsuspend(const sigset_t *mask);

参数说明:
• mask:临时信号屏蔽字

返回值:
• 总是返回-1,errno=EINTR


4.2 sigwaitinfo系统调用

功能:同步等待指定信号并获取详细信息
函数声明:

#include <signal.h>

int sigwaitinfo(const sigset_t *set,
               siginfo_t *info);

参数说明:
• set:等待信号集合
• info:接收信号详细信息

返回值:
• 成功返回信号编号,失败返回-1


4.3 sigtimedwait系统调用

功能:带超时的信号等待
函数声明:

#include <signal.h>

int sigtimedwait(const sigset_t *set,
                siginfo_t *info,
                const struct timespec *timeout);

参数说明:
• timeout:超时时间结构体

struct timespec {
    time_t tv_sec;   /* 秒 */
    long   tv_nsec;  /* 纳秒 */
};

返回值:
• 成功返回信号编号,超时返回-1且errno=EAGAIN


综合对比表

等待方式阻塞行为返回值处理适用场景
sigsuspend临时解除中断系统调用简单信号等待
sigwaitinfo持续阻塞直接返回信号编号实时信号处理
sigtimedwait超时阻塞支持超时控制需要超时机制的场景

- 阅读全文 -
系统编程基础

IO多路复用与高性能IO编程接口详解

2025-03-30 浏览量 157 暂无评论

IO多路复用与高性能IO编程接口详解

1. IO多路复用基础

IO多路复用(Multiplexing)是一种允许单个进程/线程监视多个文件描述符的机制,可以同时检测多个文件描述符是否处于可读、可写或异常状态。
水平触发LT(Level-Triggered):状态可I/O则通知
边缘触发ET(Edge-Triggered):不可I/O变成可I/O则通知(导致一次通知后必须读完)

2. select系统调用

2.1 select函数

功能:返回哪些文件描述符不会阻塞,注意只是不会阻塞,分为有数据或出错情况。可以设置超时时间。

#include <sys/select.h>

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

参数说明:
• nfds:所有被监视的文件描述符中最大的文件描述符加1
• readfds:指向可读文件描述符集合的指针,传入函数前需要指定为你想要监控读操作的文件描述符集合,返回时这里会放置可以让read不阻塞的文件描述符集合
• writefds:指向可写文件描述符集合的指针,,传入函数前需要指定为你想要监控写操作的文件描述符集合,返回时这里会放置可以让write不阻塞的文件描述符集合
• exceptfds:指向异常(并非错误)文件描述符集合的指针,传入函数前需要指定为你想要监控异常的文件描述符集合,当连接到处于信包模式下的伪终端主设备上的从设备状态发生了改变或流式套接字上接收到了带外数据时会被记录在这个集合。
• timeout:超时时间结构体指针。该参数可指定为NULL,此时select()会一直阻塞。如果结构体timeval的两个域都为0的话,此时 select()不会阻塞,它只是简单地轮询指定的文件描述符集合,看看其中是否有就绪的文件描述符并立刻返回。否则,timeout将为select()指定一个等待时间的上限值

timeval结构体:

struct timeval {
    long    tv_sec;         /* seconds */
    long    tv_usec;        /* microseconds */
};

相关宏定义:

FD_SETSIZE  // 通常为1024,定义了fd_set能容纳的最大文件描述符数量
FD_ZERO(fd_set *set)  // 清空文件描述符集合
FD_SET(int fd, fd_set *set)  // 将fd添加到集合中
FD_CLR(int fd, fd_set *set)  // 从集合中移除fd
FD_ISSET(int fd, fd_set *set)  // 检查fd是否在集合中

返回值:
• 成功:返回就绪的文件描述符总数,即三个集合内文件描述符的数量相加的结果,有可能重复
• 超时:返回0
• 出错:即任意一个或多个文件描述符被关闭则返回-1,并设置errno

3. poll系统调用

3.1 poll函数

功能:与select类似,但使用不同的文件描述符集合表示方式,没有最大文件描述符数量限制。

#include <poll.h>

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

参数说明:
• fds:指向pollfd结构体数组的指针
• nfds:fds数组中的元素个数
• timeout:超时时间(毫秒),-1表示阻塞等待,0表示立即返回

pollfd结构体:

struct pollfd {
    int   fd;         /* 文件描述符 */
    short events;     /* 等待的事件 */
    short revents;    /* 实际发生的事件 */
};

事件标志(events/revents):

宏定义值(十六进制)说明依赖宏定义
POLLIN0x0001普通或优先级带数据可读(等效于 POLLRDNORM \ POLLRDBAND)标准 POSIX
POLLPRI0x0002高优先级数据可读(如 TCP 带外数据)标准 POSIX
POLLOUT0x0004数据可写(等效于 POLLWRNORM)标准 POSIX
POLLRDNORM0x0040普通数据可读(需定义 _XOPEN_SOURCE)_XOPEN_SOURCE
POLLRDBAND0x0080优先级带数据可读(需定义 _XOPEN_SOURCE)_XOPEN_SOURCE
POLLWRNORM0x0100普通数据可写(需定义 _XOPEN_SOURCE)_XOPEN_SOURCE
POLLWRBAND0x0200优先级带数据可写(需定义 _XOPEN_SOURCE)_XOPEN_SOURCE
POLLERR0x0008发生错误(输出事件,不可在 events 中设置)标准 POSIX
POLLHUP0x0010连接挂起(输出事件,不可在 events 中设置)标准 POSIX
POLLNVAL0x0020无效文件描述符(输出事件,不可在 events 中设置)标准 POSIX
POLLRDHUP0x2000对端关闭连接或半关闭(需定义 _GNU_SOURCE,Linux 特有)_GNU_SOURCE
POLLMSG0x0400系统消息(Linux 未使用,保留)_XOPEN_SOURCE
POLLREMOVE0x1000从监控集中移除(Linux 特有,已废弃)_GNU_SOURCE(已弃用)
POLLONESHOT0x4000一次性监控(触发后自动移除,需定义 _GNU_SOURCE,Linux 特有)_GNU_SOURCE
POLLWRITE0x10000可写(非标准扩展,某些平台使用)非标准

总结以上要点,poll()真正关心的标志位就是POLLIN、POLLOUT、POLLPRI、POLLRDHUP、
POLLHUP以及POLLERR。

返回值:
• 成功:返回就绪的文件描述符数量
• 超时:返回0
• 出错:返回-1,并设置errno

4.信号驱动I/O技术深度解析

4.1、两种建立通知的方法

4.1.1方法1:基于SIGIO的异步通知

实现步骤
  1. 配置可重启的信号处理器
#include <signal.h>
struct sigaction sa;
sa.sa_flags = SA_RESTART;  // 默认自动重启被中断的系统调用
sa.sa_handler = sigio_handler;
sigemptyset(&sa.sa_mask);
sigaction(SIGIO, &sa, NULL);

关键参数:
• SA_RESTART:当信号中断低速系统调用时自动重启
• sigio_handler:自定义信号处理函数原型void handler(int sig)

  1. 设置文件属主
fcntl(fd, F_SETOWN, getpid());  // 设置进程为属主
// 或设置进程组
fcntl(fd, F_SETOWN, -getpgrp()); 

F_SETOWN参数规则:
• 正数:进程ID,
• 负数:进程组ID,注意这里进程组id若过小函数会返回错误,这是glibc实现的不足之处,具体可参考相关文档的解决办法
设置线程为属主可参考相关文档,也可避免进程组id若过小函数会返回错误

  1. 启用异步通知
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | O_ASYNC | O_NONBLOCK);

标志位说明:
• O_ASYNC:启用信号驱动I/O(原O_SYNC为笔误,正确应为O_ASYNC)
• O_NONBLOCK:保证read()在无数据时立即返回EAGAIN

4.1.2方法2:基于实时信号的高级通知

优势对比

• 信号队列化:实时信号(SIGRTMIN-SIGRTMAX)支持队列化存储,避免信号丢失
• 事件信息传递:通过siginfo_t结构携带文件描述符和事件类型
• 精确控制:可区分不同文件描述符的就绪事件

实现步骤
  1. 配置siginfo处理器
struct sigaction sa;
sa.sa_flags = SA_SIGINFO;  // 必须启用此标志
sa.sa_sigaction = rt_signal_handler; // 三参数处理函数
sigemptyset(&sa.sa_mask);
sigaction(SIGRTMIN, &sa, NULL);

处理函数原型:

void handler(int sig, siginfo_t *info, void *ucontext)
  1. 绑定实时信号
fcntl(fd, F_SETSIG, SIGRTMIN);  // 指定实时信号

F_SETSIG特性:
• 参数需≥SIGRTMIN的实时信号编号
• 设置为0时恢复默认SIGIO行为

  1. 可选同步等待方式
sigset_t waitset;
sigaddset(&waitset, SIGRTMIN);
sigprocmask(SIG_BLOCK, &waitset, NULL);

siginfo_t info;
int sig = sigwaitinfo(&waitset, &info);  // 同步阻塞等待

siginfo_t关键成员:

siginfo_t {
    int      si_signo;    // 信号编号
    int      si_fd;       // 产生信号的文件描述符
    int      si_band;     // 事件掩码(POLLIN/POLLOUT等)
}

4.2取消文件监控的方法

4.2.1 取消SIGIO监控

  1. 解除信号关联
fcntl(fd, F_SETOWN, 0);  // 清除文件属主设置
  1. 关闭异步标志
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags & ~O_ASYNC);
  1. 重置信号处理器
signal(SIGIO, SIG_DFL);  // 恢复默认处理

4.2.2 取消实时信号监控

  1. 解除信号绑定
fcntl(fd, F_SETSIG, 0);  // 还原为SIGIO
  1. 清空信号队列
sigqueue_t qinfo;
while(sigwaitinfo(&waitset, &qinfo) > 0);  // 排空已排队信号
  1. 解除信号阻塞
sigprocmask(SIG_UNBLOCK, &waitset, NULL);

4.3高并发场景应对策略

4.3.1 方法一:扩展信号队列容量

// 查看当前限制
cat /proc/sys/kernel/rtsig-max

// 临时修改限制(需root权限)
echo 1000000 > /proc/sys/kernel/rtsig-max

// 永久修改(在/etc/sysctl.conf添加)
kernel.rtsig-max = 1000000

相关内核参数:
• rtsig-max:最大排队信号数(默认值通常为1024)
• rtsig-nr:当前排队信号数(通过/proc/sys/kernel/rtsig-nr查看)

4.3.2 方法二:混合信号与轮询机制

  1. 配置SIGIO兜底处理
signal(SIGIO, overflow_handler);  // 信号队列满时触发
  1. 在处理器中启用轮询
void overflow_handler(int sig) {
    struct pollfd pfds[MAX_FDS];
    // 填充所有监控的fd
    poll(pfds, num_fds, 0);  // 非阻塞检查
}
  1. 事件检测逻辑
// 在poll返回后遍历所有fd
for(int i=0; i<num_fds; i++) {
    if(pfds[i].revents & POLLIN) {
        // 处理读就绪事件
    }
}

4.4关键函数技术规格

fcntl() 扩展说明

#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
命令参数类型作用域
F_SETOWNint设置进程/进程组属主
F_SETSIGint绑定实时信号
F_GETFLvoid获取文件状态标志
F_SETFLint设置文件状态标志

sigaction() 结构体详解

struct sigaction {
    void     (*sa_handler)(int);
    void     (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t   sa_mask;
    int        sa_flags;
    void     (*sa_restorer)(void);
};

sa_flags关键标志组合:
• SA_RESTART | SA_SIGINFO:支持自动重启并携带扩展信息
• SA_NODEFER:不自动阻塞当前信号类型

事件掩码宏定义

标志位值(十六进制)对应事件
POLLIN0x0001普通或高优先级数据可读
POLLPRI0x0002高优先级数据可读
POLLOUT0x0004写数据不会导致阻塞
POLLERR0x0008发生错误
POLLHUP0x0010连接挂起
POLLNVAL0x0020无效的文件描述符

5. epoll接口

select与poll是库函数进行循环调用系统调用来返回就绪的fd,而epoll直接是内核根据其维护的打开文件句柄表来创建红黑树实现监控。select与poll干活的人是库,epoll干活的是内核。

5.1 epoll_create/epoll_create1

功能:创建epoll实例。

#include <sys/epoll.h>

int epoll_create(int size);
int epoll_create1(int flags);

参数说明:
• size:提示内核期望监控的文件描述符数量(已废弃),随便你怎么指定
• flags:
• EPOLL_CLOEXEC:设置close-on-exec标志,目前只实现了这个

返回值:
• 成功:返回epoll文件描述符
• 失败:返回-1,并设置errno

5.2 epoll_ctl

功能:向epoll实例添加、修改或删除文件描述符。当内核维护的打开文件描述的一项文件句柄没有任何进程的fd指向它时,内核在删除该文件句柄的同时删除epoll实例对其的监控(从红黑树中删除)

#include <sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

参数说明:
• epfd:epoll文件描述符
• op:操作类型:
• EPOLL_CTL_ADD:添加文件描述符
• EPOLL_CTL_MOD:修改文件描述符
• EPOLL_CTL_DEL:删除文件描述符
• fd:要操作的目标文件描述符
• event:指向epoll_event结构体的指针

epoll_event结构体:

struct epoll_event {
    uint32_t     events;    /* Epoll events */
    epoll_data_t data;      /* User data variable */
};

typedef union epoll_data {
    void        *ptr;
    int          fd; //监控文件描述符需要用这个
    uint32_t     u32;
    uint64_t     u64;
} epoll_data_t;

使用时既设置参数fd为要监控的fd,又设置struct event结构体的epoll_data_t为要监控的fd

事件标志(events)(仅仅较poll的事件前面加了一个'e'):

EPOLLIN      // 可读事件
EPOLLOUT     // 可写事件
EPOLLRDHUP   // 对端关闭连接或关闭写操作
EPOLLPRI     // 紧急数据可读
EPOLLERR     // 错误条件发生
EPOLLHUP     // 挂起发生
EPOLLET      // 设置边缘触发模式(注意要读完数据的同时防止一个文件描述符上源源不断有数据导致其他文件描述符饥饿)
EPOLLONESHOT // 设置一次性监听

返回值:
• 成功:返回0
• 失败:返回-1,并设置errno

5.3 epoll_wait

功能:等待epoll文件描述符上的IO事件。

#include <sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event *events,
               int maxevents, int timeout);

参数说明:
• epfd:epoll文件描述符
• events:用于返回事件的数组
• maxevents:events数组的大小,起码大于你往epoll实例里面添加的文件描述符个数
• timeout:超时时间(毫秒),-1表示阻塞,0表示立即返回

返回值:
• 成功:返回就绪的文件描述符数量
• 超时:返回0
• 出错:返回-1,并设置errno

5.4 epoll_pwait

功能:与epoll_wait类似,但允许指定信号掩码。

#include <sys/epoll.h>

int epoll_pwait(int epfd, struct epoll_event *events,
                int maxevents, int timeout,
                const sigset_t *sigmask);

参数说明:
• sigmask:指向信号掩码的指针

返回值:同epoll_wait

6. 性能比较

  1. select:
    • 优点:跨平台支持好
    • 缺点:文件描述符数量有限(FD_SETSIZE),每次调用需要重置参数为自己想要监控的文件描述符集合,函数也需循环nfds次来先判断该文件描述符是否在用户指定的集合中,函数执行的时间复杂度为O(n),对进程内存造成的空间复杂度为O(n)
  2. poll:
    • 优点:无最大文件描述符限制,较select可以指定每个文件描述符的监控偏好,函数可以不用循环0到nfds。
    • 缺点:函数需要遍历整个fds数组,时间复杂度O(n),对进程内存造成的空间复杂度为O(n)
  3. epoll:
    • 优点:时间复杂度O(1),支持边缘触发模式,无文件描述符数量限制
    • 缺点:Linux特有,不跨平台
  4. 信号驱动IO:
    • 优点:真正的异步通知机制
    • 缺点:编程模型复杂,信号处理有诸多限制

- 阅读全文 -
  1. 1
  2. 2
  3. 3
  4. 4

浏览量 : 5445

© 2025 It's Geek KingYoungy. Power By Typecho . Theme by Shiyi

浙ICP备2025160639号  |  浙公网安备33020502001222号

This is just a placeholder img.