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 协议进行许可,转载请注明出处。