信号处理与进程控制:C语言标准库深入解析指南
发布时间: 2024-12-09 18:25:15 阅读量: 13 订阅数: 11
scpi-parser-master_SCPI语法解析程序_SCPI_C语言SCPI_Scpiserver
5星 · 资源好评率100%
![信号处理与进程控制:C语言标准库深入解析指南](https://img-blog.csdnimg.cn/7e23ccaee0704002a84c138d9a87b62f.png)
# 1. 信号处理机制基础
## 1.1 信号的定义与分类
信号是操作系统用来通知进程发生了某个事件的一种机制。比如用户按下了Ctrl+C,操作系统会向当前正在运行的进程发送SIGINT信号,通知进程该事件。
### 1.1.1 信号的产生与传递
信号产生于内核,如键盘输入、硬件异常等。信号的传递是异步的,即信号产生时,进程可能正在执行任何指令,内核负责将信号挂起至进程的信号队列中。
### 1.1.2 标准信号及其含义
标准信号是预定义的一系列信号,例如SIGINT用于中断当前进程、SIGSEGV表示访问违规。完整的信号列表可以在系统头文件`<signal.h>`中找到,每个信号都有自己的编号和含义。
## 1.2 信号处理的策略
操作系统提供了一套信号处理的默认策略,但用户也可以通过编程自定义信号处理函数,或者选择忽略特定的信号。
### 1.2.1 默认信号处理行为
大多数信号的默认行为是终止进程,但并非所有信号都是如此,例如SIGCHLD默认行为是忽略。
### 1.2.2 自定义信号处理函数
使用`signal()`或`sigaction()`函数可以设置信号处理函数,捕捉到信号时,执行用户定义的处理逻辑。
### 1.2.3 忽略信号的处理方式
对于一些不希望程序响应的信号,可以将其设置为忽略。但是SIGKILL和SIGSTOP信号无法被忽略。
## 1.3 信号阻塞与递送机制
信号阻塞是指进程可以临时忽略某些信号,直到它解除阻塞。信号递送机制涉及到信号的存储和最终的处理时机。
### 1.3.1 信号阻塞的原理与实现
通过`sigprocmask()`函数来实现信号的阻塞与解阻塞。阻塞信号可以在特定的代码块中临时生效,从而保证数据一致性或保护临界区。
### 1.3.2 信号递送的时序与问题
信号的递送时机受多种因素影响,可能导致信号处理函数的执行时机不确定性,需要特别注意处理竞态条件和死锁情况。
# 2. 进程控制与进程间通信
## 2.1 进程的创建与终止
在操作系统中,进程是程序的执行实例,它包含了程序代码以及其当前的活动。进程的生命周期包括创建、运行、等待、终止等状态。理解进程的创建与终止对于深入学习计算机科学和系统编程至关重要。
### 2.1.1 fork()、exec()系列函数与进程创建
Linux环境下,`fork()`函数是进程创建的标准方式。当调用`fork()`时,当前进程会创建一个新的子进程,这个子进程几乎是父进程的完整副本,拥有自己的进程标识符(PID)和内存空间。子进程和父进程在`fork()`之后会执行相同的代码,但是可以通过返回值区分彼此。
```c
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork();
if (pid == -1) {
// fork失败
perror("fork failed");
return 1;
} else if (pid == 0) {
// 子进程中执行的代码
printf("This is the child process with PID %d\n", getpid());
} else {
// 父进程中执行的代码
printf("This is the parent process with PID %d\n", getpid());
// 等待子进程结束
wait(NULL);
}
return 0;
}
```
上面的代码中,`fork()`函数返回的是子进程的PID给父进程,返回0给子进程。通过判断返回值,可以区分是父进程还是子进程执行的代码块。需要注意的是,`fork()`仅在Linux和类Unix系统中可用。
`exec()`系列函数则是用来执行新的程序的。当`exec()`函数在一个进程中被调用时,该进程的当前程序会被新的程序覆盖,但是进程的PID和其它资源不变。`exec()`系列函数有多个变体,例如`execl()`, `execp()`, `execle()`, `execlp()`, `execv()` 和 `execvp()`,它们之间主要区别在于参数的传递方式。
### 2.1.2 wait()、exit()函数与进程终止
`wait()`函数是进程终止后需要调用的,它用于等待子进程结束。一个进程在结束前会调用`exit()`来结束自己的执行。`wait()`函数的调用会阻塞父进程直到它的某个子进程结束,此时`wait()`函数返回被结束子进程的PID。
```c
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
int main() {
pid_t pid = fork();
if (pid == -1) {
// fork失败
perror("fork failed");
return 1;
} else if (pid == 0) {
// 子进程执行的代码
printf("Child process is exiting\n");
exit(0);
} else {
// 父进程执行的代码
printf("Parent process is waiting for child to complete\n");
wait(NULL); // 等待子进程结束
printf("Child complete\n");
}
return 0;
}
```
这个程序创建了一个子进程,然后子进程打印一条消息后退出。父进程则在调用`wait()`后打印另一条消息。`wait(NULL)`会返回一个子进程的PID,其中`NULL`是告诉`wait()`不需要收集子进程结束时的信息。
`exit()`函数将终止调用进程,并向父进程返回一个退出状态。在C语言中,通常使用宏定义`EXIT_SUCCESS`和`EXIT_FAILURE`来表示不同的退出状态。
## 2.2 进程间通信机制
进程间通信(IPC)是进程之间进行交互的一种机制,允许一个进程与其它进程共享数据。主要的IPC机制包括管道、消息队列、共享内存和信号量。
### 2.2.1 管道、消息队列与共享内存
- 管道(Pipe)是一种最基本的IPC机制,用于连接一个读进程和一个写进程以实现它们之间的双向数据流。
- 消息队列(Message Queue)允许不同进程通过消息传递进行通信。消息队列由消息组成,这些消息可以独立于发送和接收进程,甚至可以在它们运行之前存储在队列中。
- 共享内存(Shared Memory)允许一个或多个进程共享一个给定的存储区,这是最快的IPC方法,因为进程是直接对内存进行读写,而无需通过内核进行消息传递。
### 2.2.2 信号量的同步与互斥机制
信号量是一种特殊的变量,可以用来进行进程同步和互斥。同步指的是合作进程间的协调动作,例如,确保对共享资源的访问不会导致冲突。互斥则保证在任何时刻只有一个进程可以访问共享资源。在Linux中,信号量由`semget()`, `semop()`, `semctl()` 等系统调用实现。
## 2.3 进程调度与控制
进程调度是操作系统内核的一部分,它决定了哪个进程获得CPU的执行时间。进程调度是多任务操作系统的核心功能,而进程控制则涉及进程的创建、挂起、恢复和终止等。
### 2.3.1 进程优先级的调整与调度
每个进程都有一个优先级,系统通过优先级来决定哪个进程先获得CPU。在Linux系统中,可以使用`nice`和`renice`命令来调整进程的优先级。优先级的范围通常是从-20(最高优先级)到19(最低优先级),默认优先级是0。
调度策略决定了进程获得CPU时间的方式,常见的策略有:
- SCHED_FIFO(先入先出)
- SCHED_RR(循环调度)
- SCHED_OTHER(普通调度)
- SCHED_BATCH(批量作业调度)
- SCHED_IDLE(空闲时间调度)
### 2.3.2 进程组和会话的管理
进程组是一组在一个进程中执行的进程,它们可以接收信号作为一个整体。会话是一组进程组,通常由一个用户登录会话产生。使用`setsid()`函数可以创建一个新的会话。
这些概念在设计需要良好组织和管理的复杂应用程序时非常重要。例如,一个守护进程可以作为一个独立的会话运行,这样即使启动它的终端关闭了,守护进程也不会受到影响。
在上述章节中,我们探讨了进程的创建和终止,以及它们之间的通信方式。这些内容是操作系统和多任务编程的基础,并且为更深入理解高级主题(比如信号处理、进程调度等)提供了坚实的基础。随着本章节的结束,我们将进一步深入探讨C语言标准库中有关信号和进程控制的函数,并在后续章节中深入分析其在复杂场景中的应用。
# 3. C语言标准库的信号处理函数
### 3.1 signal()函数及其变体
信号处理是任何健壮的系统级编程不可或缺的一部分。C语言通过一系列的函数为开发者提供了完整的控制能力,其中signal()函数是最基本也是最常用的信号处理函数之一。
#### 3.1.1 signal()的基本使用方法
signal()函数用于设置对某一特定信号的处理方式,其原型定义在<signal.h>头文件中。该函数接收两个参数:信号值和处理该信号的函数。以下是一个简单的使用示例:
```c
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void signal_handler(int signo) {
printf("Signal %d received.\n", signo);
}
int main() {
signal(SIGINT, signal_handler); // 设置SIGINT信号的处理函数
while(1) {
sleep(1);
}
return 0;
}
```
代码段中定义了一个信号处理函数`signal_handler`,该函数用于处理接收到的信号。`signal()`函数将SIGINT信号与`signal_handler`函数关联起来,表示当SIGINT信号发生时,系统会调用`signal_handler`函数。
#### 3.1.2 signal()的局限性与改进
虽然signal()函数的使用非常简单,但它有一些局限性。信号处理函数必须是可重入的,而标准C库并没有强制要求这一点。此外,signal()函数的行为在不同的操作系统或编译器上可能存在差异,这使得它难以移植。为此,sigaction()函数应运而生,它提供了一种更为可靠和可移植的方式来设置信号处理动作。
### 3.2 sigaction()的高级特性
sigaction()函数为信号处理提供了更多的控制能力。它不仅可以用于设置信号处理函数,还可以管理信号集以及其它信号相关的选项。
#### 3.2.1 sigaction()与信号集操作
sigaction()函数使用一个结构体来定义信号的处理方式,它支持信号集的操作,比如阻塞信号的传递。以下是一个使用sigaction()的示例代码:
```c
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void sig_handler(int s
```
0
0