
在嵌入式 Linux 应用开发中,信号量是一种常用的进程间通信(IPC)机制,用于实现进程之间的同步和互斥。
信号量(Semaphore)本质上是一个计数器,它的值表示系统中某种资源的数量。信号量有两种类型:
信号量的工作原理基于两种操作:等待(P操作)和发送(V操作)。
在 Linux 系统中,信号量的操作主要通过 sys/sem.h 头文件中定义的函数来实现,常见的函数有:
①semget():创建或获取一个信号量集。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);key:是一个唯一标识信号量集的键值,可以通过 ftok() 函数生成。
nsems:指定信号量集中信号量的数量。
semflg:标志位,用于指定创建或获取信号量集的权限和选项。如果是创建新的信号量集,semflg 通常设置为 IPC_CREAT | 0666(0666 表示权限)。
②semop():对信号量集中的信号量进行操作,如 P 操作(等待信号量,资源减 1)和 V 操作(释放信号量,资源加 1)。
#include <sys/types.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);semid:信号量集的标识符,由 semget() 函数返回。sops:指向一个 struct sembuf 结构体数组的指针,struct sembuf 结构体定义了对每个信号量的操作。nsops:表示 sops 数组中元素的数量。struct sembuf 结构体的定义如下:
struct sembuf {
unsigned short sem_num; // 信号量集中信号量的编号(从 0 开始)
short sem_op; // 操作值,P 操作为 -1,V 操作为 +1
short sem_flg; // 操作标志,如 IPC_NOWAIT(非阻塞操作)
};③semctl():用于控制信号量集,如初始化信号量的值、获取信号量的状态等。
#include <sys/types.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);semid:信号量集的标识符。semnum:信号量集中信号量的编号(从 0 开始)。cmd:指定要执行的操作命令,如 SETVAL(设置信号量的值)、GETVAL(获取信号量的值)等。...:根据 cmd 的不同,可能需要额外的参数。cmd 的不同,返回值有所不同,成功时返回相应的结果,失败时返回 -1。#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
// 定义信号量操作函数
void semaphore_p(int semid) {
struct sembuf sem_op;
sem_op.sem_num = 0; // 信号量编号为 0
sem_op.sem_op = -1; // P 操作
sem_op.sem_flg = 0;
semop(semid, &sem_op, 1);
}
void semaphore_v(int semid) {
struct sembuf sem_op;
sem_op.sem_num = 0; // 信号量编号为 0
sem_op.sem_op = 1; // V 操作
sem_op.sem_flg = 0;
semop(semid, &sem_op, 1);
}
int main() {
key_t key;
int semid;
// 生成唯一的键值
key = ftok(".", 'a');
if (key == -1) {
perror("ftok");
exit(1);
}
// 创建信号量集
semid = semget(key, 1, IPC_CREAT | 0666);
if (semid == -1) {
perror("semget");
exit(1);
}
// 初始化信号量的值为 1(二进制信号量,用于互斥)
if (semctl(semid, 0, SETVAL, 1) == -1) {
perror("semctl");
exit(1);
}
pid_t pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
} else if (pid == 0) { // 子进程
semaphore_p(semid);
printf("Child process: Entering critical section\n");
sleep(2); // 模拟在临界区的操作
printf("Child process: Leaving critical section\n");
semaphore_v(semid);
} else { // 父进程
semaphore_p(semid);
printf("Parent process: Entering critical section\n");
sleep(1); // 模拟在临界区的操作
printf("Parent process: Leaving critical section\n");
semaphore_v(semid);
}
// 删除信号量集
if (semctl(semid, 0, IPC_RMID) == -1) {
perror("semctl");
exit(1);
}
return 0;
}父子进程通过信号量实现了对临界区的互斥访问。父进程和子进程在进入临界区之前都先执行 P 操作获取信号量,离开临界区时执行 V 操作释放信号量,从而保证同一时刻只有一个进程能够进入临界区。
sem_t mutex;
sem_init(&mutex, 1, 1); // 初始化为1(跨进程)
// 进程A
sem_wait(&mutex); // 进入临界区
access_shared_resource();
sem_post(&mutex); // 退出临界区
// 进程B同理empty:表示空闲缓冲区数量(初始值为缓冲区大小)。
full:表示已填充缓冲区数量(初始值为0)。
sem_t empty, full;
sem_init(&empty, 1, BUFFER_SIZE); // 初始空闲缓冲区数量
sem_init(&full, 1, 0); // 初始已填充数量
// 生产者
sem_wait(&empty); // 等待空闲缓冲区
write_to_buffer();
sem_post(&full); // 增加已填充计数
// 消费者
sem_wait(&full); // 等待有数据的缓冲区
read_from_buffer();
sem_post(&empty); // 释放空闲缓冲区sem_t sync_sem;
sem_init(&sync_sem, 1, 0); // 初始值为0
// 每个线程完成初始化后调用
sem_post(&sync_sem);
// 主线程等待所有线程完成
for (int i=0; i<3; i++) {
sem_wait(&sync_sem);
}sem_t pool;
sem_init(&pool, 1, MAX_CONNECTIONS); // 初始为最大连接数
// 申请资源
sem_wait(&pool); // 资源数-1
use_connection();
sem_post(&pool); // 资源数+1rw_mutex:读写互斥(初始值为1)。
read_count_mutex:保护读者计数(初始值为1)。
sem_t rw_mutex, read_count_mutex;
int read_count = 0;
// 读者
sem_wait(&read_count_mutex);
read_count++;
if (read_count == 1) sem_wait(&rw_mutex); // 第一个读者锁写
sem_post(&read_count_mutex);
// 读操作...
sem_wait(&read_count_mutex);
read_count--;
if (read_count == 0) sem_post(&rw_mutex); // 最后一个读者解锁写
sem_post(&read_count_mutex);
// 写者
sem_wait(&rw_mutex);
// 写操作...
sem_post(&rw_mutex);sem_timedwait)。
semctl(..., IPC_RMID)显式删除。
sem_close后,至少一个进程调用sem_unlink。
sem_destroy销毁前确保无线程在等待。
/dev/shm)中持久化,需手动清理残留。
ipcrm命令删除。
if (sem_wait(sem) == -1) {
perror("sem_wait failed");
// 处理错误(如超时或信号中断)
}semop)。
信号量是嵌入式Linux中解决并发问题的核心工具,正确使用需遵循以下原则:
①场景驱动选择:
②资源管理铁律:
sem_wait/sem_post)。
③嵌入式系统特殊考量:
通过结合其他IPC机制(如共享内存+信号量),可构建高效可靠的多任务系统。