1. 信号与信号量的概念
在Linux系统中,信号(Signal)是进程间通信的一种机制,用于向进程发送通知或中断信号。信号可以是系统发出的或由用户进程发出的,它可以用于一些特定事件的通知,比如:按下Ctrl+C键,终端会向当前运行的进程发送一个SIGINT信号,用于请求进程终止。
信号量(Semaphore)是一种计数机制,用于控制多个进程对共享资源的访问。信号量可以用来实现进程间的互斥和同步操作。当一个进程将信号量减1并进入临界区时,其他试图进入临界区的进程必须等待该进程离开临界区并将信号量加1后才能进入。
2. 信号的作用
2.1 程序的异常处理
信号在程序中起到了异常处理的作用。当程序发生异常情况时,操作系统会发送一个相应的信号给程序,程序可以选择对该信号进行处理。例如,在程序发生除0错误时,操作系统会发送SIGFPE信号给程序,程序可以捕获该信号并进行相应的处理,避免程序崩溃。
以下是一个示例,展示了如何在程序中捕获并处理SIGFPE信号:
#include <signal.h>
#include <stdio.h>
void sigfpe_handler(int signum) {
printf("除0错误发生!\n");
// 可进行异常处理逻辑
}
int main() {
// 注册SIGFPE信号的处理函数
signal(SIGFPE, sigfpe_handler);
int a = 10, b = 0;
int result = a/b; // 除0错误
return 0;
}
上述代码中,定义了一个自定义处理函数sigfpe_handler,当SIGFPE信号出现时,调用该函数进行异常处理。在主函数中通过signal函数将SIGFPE信号与sigfpe_handler函数关联起来,实现了对该信号的捕获和处理。
2.2 进程间通信
信号还可以用于实现进程间的通信。一个进程可以向另一个进程发送信号,以通知特定事件的发生或请求某种操作。例如,我们可以通过向某个进程发送SIGTERM信号来请求该进程正常退出。
下面是一个示例,展示了如何通过kill函数向其他进程发送信号:
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
printf("子进程正在运行...\n");
sleep(5);
printf("子进程运行结束。\n");
} else if (pid > 0) {
sleep(1);
printf("父进程发送SIGKILL信号给子进程。\n");
kill(pid, SIGKILL);
} else {
perror("fork");
return -1;
}
return 0;
}
上述代码中,父进程通过fork函数创建了一个子进程。父进程暂停1秒后,向子进程发送SIGKILL信号,强制终止子进程的运行。子进程在收到SIGKILL信号后会立即终止。
3. 信号量的作用
3.1 进程间的互斥访问
信号量可以用于实现多个进程对共享资源的互斥访问。一个进程在访问共享资源之前,需要先获得信号量的权限,即将信号量减1。其他试图获得信号量的进程必须等待,直到信号量的值大于0。
以下是一个示例,展示了如何使用信号量实现进程间的互斥访问:
#include <unistd.h>
#include <sys/sem.h>
#include <stdio.h>
int main() {
int semid = semget(IPC_PRIVATE, 1, IPC_CREAT | 0666); // 创建信号量
// 初始化信号量的值为1,表示可以访问共享资源
semctl(semid, 0, SETVAL, 1);
pid_t pid = fork();
if (pid == 0) {
// 子进程
printf("子进程等待访问共享资源...\n");
struct sembuf sem_op;
sem_op.sem_num = 0;
sem_op.sem_op = -1;
sem_op.sem_flg = SEM_UNDO;
semop(semid, &sem_op, 1); // 等待信号量变为大于0
printf("子进程开始访问共享资源。\n");
// 子进程访问共享资源的代码
// ...
printf("子进程结束访问共享资源。\n");
semop(semid, &sem_op, 1); // 释放信号量,增加其值
} else if (pid > 0) {
// 父进程
printf("父进程等待访问共享资源...\n");
struct sembuf sem_op;
sem_op.sem_num = 0;
sem_op.sem_op = -1;
sem_op.sem_flg = SEM_UNDO;
semop(semid, &sem_op, 1); // 等待信号量变为大于0
printf("父进程开始访问共享资源。\n");
// 父进程访问共享资源的代码
// ...
printf("父进程结束访问共享资源。\n");
semop(semid, &sem_op, 1); // 释放信号量,增加其值
} else {
perror("fork");
return -1;
}
// 删除信号量
semctl(semid, 0, IPC_RMID);
return 0;
}
上述代码中,使用semget函数创建了一个信号量,初始值为1,表示可以访问共享资源。在子进程和父进程中,分别使用semop函数等待信号量的值大于0,然后访问共享资源并最后释放信号量。
3.2 进程间的同步操作
信号量还可以用于实现多个进程之间的同步操作。一个进程通过等待一个信号量的变为非0,可以等待另一个进程的某个事件完成。当信号量的值变为非0时,表示另一个进程的事件已经完成。
以下是一个示例,展示了如何使用信号量实现进程间的同步操作:
#include <sys/sem.h>
#include <stdio.h>
int main() {
int semid = semget(IPC_PRIVATE, 2, IPC_CREAT | 0666); // 创建两个信号量
// 初始化信号量的值为0
semctl(semid, 0, SETVAL, 0); // 用于等待进程1完成
semctl(semid, 1, SETVAL, 0); // 用于等待进程2完成
pid_t pid1, pid2;
pid1 = fork();
if (pid1 == 0) {
// 子进程1
printf("子进程1运行。\n");
// 子进程1的代码
// ...
semctl(semid, 0, SETVAL, 1); // 通知进程2可以开始运行
} else if (pid1 > 0) {
pid2 = fork();
if (pid2 == 0) {
// 子进程2
struct sembuf sem_op;
sem_op.sem_num = 0;
sem_op.sem_op = -1;
sem_op.sem_flg = SEM_UNDO;
semop(semid, &sem_op, 1); // 等待进程1完成
printf("子进程2运行。\n");
// 子进程2的代码
// ...
semctl(semid, 1, SETVAL, 1); // 通知进程1可以结束
} else if (pid2 > 0) {
// 父进程
struct sembuf sem_op;
sem_op.sem_num = 1;
sem_op.sem_op = -1;
sem_op.sem_flg = SEM_UNDO;
semop(semid, &sem_op, 1); // 等待进程2完成
printf("父进程运行。\n");
// 父进程的代码
// ...
semctl(semid, 0, IPC_RMID); // 删除信号量
} else {
perror("fork");
return -1;
}
} else {
perror("fork");
return -1;
}
return 0;
}
上述代码中,创建了两个信号量,分别用于等待进程1完成和等待进程2完成。在各个进程中,使用semop函数等待或通知其他进程的进行。