Linux 系统编程中 睡眠/不可睡眠、原子/非原子、阻塞/非阻塞、同步/异步以及可重入/不可重入等概念解析

Linux 系统编程核心概念详解

1. 睡眠/不可睡眠(Sleeping/Non-sleeping)

概念解释

  • 可睡眠:进程可以主动放弃CPU,进入睡眠状态等待某个条件满足
  • 不可睡眠:不能进入睡眠状态,必须立即完成(通常在中断上下文中)

通俗解释

就像你在等外卖:

  • 可睡眠:你可以去睡觉,外卖到了再叫醒你
  • 不可睡眠:你必须一直站在门口等着,不能离开

代码示例

// 可睡眠的情况 - 用户空间或进程上下文
#include <unistd.h>
#include <stdio.h>

void sleepable_function() {
    printf("开始等待...\n");
    sleep(2);  // 可以睡眠2秒
    printf("等待结束\n");
}

// 不可睡眠的情况 - 中断处理函数中
static irqreturn_t my_interrupt_handler(int irq, void *dev_id) {
    // 这里不能调用 sleep()、mutex_lock() 等可能睡眠的函数
    // 只能使用 spin_lock() 等不会睡眠的操作
    spin_lock(&my_lock);
    // 快速处理
    spin_unlock(&my_lock);
    return IRQ_HANDLED;
}

2. 原子/非原子(Atomic/Non-atomic)

概念解释

  • 原子操作:不可分割的操作,要么全部完成,要么全部不做
  • 非原子操作:可能被中断的操作,执行过程可能被打断

通俗解释

就像转账:

  • 原子操作:要么钱转成功了,要么没转,不会出现钱扣了但没到账的情况
  • 非原子操作:可能出现钱扣了但系统崩溃导致没到账的情况

代码示例

#include <stdatomic.h>
#include <pthread.h>

// 原子操作
atomic_int counter = 0;

void* atomic_increment(void* arg) {
    for(int i = 0; i < 1000000; i++) {
        atomic_fetch_add(&counter, 1);  // 原子加1
    }
    return NULL;
}

// 非原子操作
int normal_counter = 0;

void* non_atomic_increment(void* arg) {
    for(int i = 0; i < 1000000; i++) {
        normal_counter++;  // 非原子操作,可能导致竞态条件
        // 实际上等价于:
        // temp = normal_counter;
        // temp = temp + 1;
        // normal_counter = temp;
    }
    return NULL;
}

3. 阻塞/非阻塞(Blocking/Non-blocking)

概念解释

  • 阻塞:调用会等待直到操作完成才返回
  • 非阻塞:调用立即返回,不管操作是否完成

通俗解释

就像去银行办业务:

  • 阻塞:你必须排队等待,直到轮到你办完才能离开
  • 非阻塞:你拿个号就走,不用在现场等,可以去做别的事

代码示例

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

// 阻塞读取
void blocking_read() {
    char buffer[100];
    int fd = open("/dev/input/mice", O_RDONLY);  // 默认是阻塞的

    // 会一直等待直到有数据可读
    ssize_t bytes = read(fd, buffer, sizeof(buffer));
    printf("读取了 %ld 字节\n", bytes);
    close(fd);
}

// 非阻塞读取
void non_blocking_read() {
    char buffer[100];
    int fd = open("/dev/input/mice", O_RDONLY | O_NONBLOCK);  // 非阻塞模式

    ssize_t bytes = read(fd, buffer, sizeof(buffer));
    if (bytes == -1 && errno == EAGAIN) {
        printf("现在没有数据可读\n");  // 立即返回
    } else if (bytes > 0) {
        printf("读取了 %ld 字节\n", bytes);
    }
    close(fd);
}

4. 同步/异步(Synchronous/Asynchronous)

概念解释

  • 同步:调用方等待被调用方完成后才继续执行
  • 异步:调用方不等待,被调用方完成后通过回调或信号通知

通俗解释

就像叫外卖:

  • 同步:你打电话订餐,一直拿着电话等餐厅确认订单后才挂断
  • 异步:你在APP下单后就去做别的事,外卖到了APP会通知你

代码示例

#include <aio.h>
#include <signal.h>
#include <string.h>

// 同步写入
void sync_write() {
    int fd = open("test.txt", O_WRONLY | O_CREAT, 0644);
    const char* data = "Hello, World!";

    // write() 是同步的,写完才返回
    ssize_t result = write(fd, data, strlen(data));
    printf("同步写入完成:%ld 字节\n", result);
    close(fd);
}

// 异步写入
void async_write_complete(sigval_t sigval) {
    printf("异步写入完成!\n");
}

void async_write() {
    struct aiocb cb;
    const char* data = "Hello, Async!";

    memset(&cb, 0, sizeof(struct aiocb));
    cb.aio_fildes = open("test_async.txt", O_WRONLY | O_CREAT, 0644);
    cb.aio_buf = data;
    cb.aio_nbytes = strlen(data);
    cb.aio_sigevent.sigev_notify = SIGEV_THREAD;
    cb.aio_sigevent.sigev_notify_function = async_write_complete;

    // aio_write() 立即返回,写入在后台进行
    aio_write(&cb);
    printf("异步写入已启动,继续做其他事...\n");

    // 可以继续执行其他代码
    sleep(1);  // 等待异步操作完成
}

5. 可重入/不可重入(Reentrant/Non-reentrant)

概念解释

  • 可重入:函数可以被中断后再次调用,不会出现问题
  • 不可重入:函数不能在执行过程中被再次调用

通俗解释

就像使用公共电话:

  • 可重入:每个人都有自己的电话卡,可以同时打电话互不影响
  • 不可重入:只有一部电话,一个人用的时候其他人不能用

代码示例

#include <stdio.h>
#include <string.h>

// 不可重入函数 - 使用静态变量
char* non_reentrant_function(int num) {
    static char buffer[100];  // 静态缓冲区,所有调用共享
    sprintf(buffer, "Number is: %d", num);
    return buffer;  // 返回静态缓冲区的地址
}

// 可重入函数 - 不使用静态变量
void reentrant_function(int num, char* buffer, size_t size) {
    snprintf(buffer, size, "Number is: %d", num);
    // 使用调用者提供的缓冲区,每次调用都是独立的
}

// 使用示例
void demonstrate_reentrancy() {
    // 不可重入函数的问题
    char* result1 = non_reentrant_function(10);
    char* result2 = non_reentrant_function(20);
    printf("Result1: %s\n", result1);  // 输出: Number is: 20 (被覆盖了!)
    printf("Result2: %s\n", result2);  // 输出: Number is: 20

    // 可重入函数的正确使用
    char buffer1[100], buffer2[100];
    reentrant_function(10, buffer1, sizeof(buffer1));
    reentrant_function(20, buffer2, sizeof(buffer2));
    printf("Buffer1: %s\n", buffer1);  // 输出: Number is: 10
    printf("Buffer2: %s\n", buffer2);  // 输出: Number is: 20
}

总结对比表

概念对比特点使用场景注意事项
可睡眠可以放弃CPU等待进程上下文不能在中断中使用
不可睡眠必须立即完成中断处理、持有自旋锁时执行要快
原子操作不可分割多线程计数器、标志位性能开销较大
非原子操作可被中断单线程或有锁保护的场景需要同步机制
阻塞等待操作完成I/O操作、等待资源可能导致死锁
非阻塞立即返回高性能服务器、GUI程序需要轮询或事件机制
同步顺序执行简单的顺序逻辑可能降低性能
异步并发执行高并发、I/O密集型编程复杂度高
可重入可被多次调用信号处理、多线程不能使用静态变量
不可重入不能被重复调用单线程、有锁保护使用要小心

本文版权归原作者zhaofujian所有,采用 CC BY-NC-ND 4.0 协议进行许可,转载请注明出处。

发表评论