目录
5.4 其他IPC机制
5.4.1 信号
5.4.2 管道和套接字
5.5 小结
本专栏文章将有70篇左右,欢迎+关注,查看后续文章。
5.4 其他IPC机制
5.4.1 信号
kill命令:
作用:发送指定信号。
信号分为:
传统32个信号。
用于实时进程的RT新信号。
进程可屏蔽(阻塞)指定信号。
信号被屏蔽后,内核将信号放在待决列表上。而相同信号只会往待决列表放置一次。
所以不管发送了多少个相同信号,进程解除阻塞后,都只会收到一个信号。
进程无法阻塞SIGKILL信号。
但init进程是特例。init进程会忽略收到的SIGKILL信号。
因为init进程不能结束。
实现信号处理程序
struct sigaction {
void (*sa_handler)(int); //传统信号的处理函数。
void (*sa_sigaction)(int, siginfo_t *, void *); //扩展信号的处理函数。
sigset_t sa_mask; //屏蔽的信号集合。
int sa_flags; //信号处理行为标志,如SA_RESTART等。
};
sa_handler和sa_sigaction使用场景的区别:
1. sa_handler:
不需要获取信号的额外信息,处理简单。只有一个参数(信号编号)。
2. sa_sigaction:
需要获取信号的详细信息。如发送信号的进程PID,信号产生原因等。
1. sa_handler编程举例:
int main() {
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset( &sa.sa_mask );
//sa.sa_mask:用于指定要屏蔽的信号集合。
//此处清空,表示不屏蔽任何信号。
sigaddset(&sa.sa_mask, SIGUSR1); //屏蔽SIGUSR1信号。
sigaction(SIGINT, &sa, NULL) ;
}
void signal_handler(int sig)
{
...
}
2. sa_handler编程举例:
int main() {
struct sigaction sa;
sa.sa_sigaction = signal_handler;
sigemptyset( &sa.sa_mask );
sa.sa_flags = SA_SIGINFO | SA_RESTART;
sigaction(SIGSEGV, &sa, NULL);
}
void signal_handler(int signum, siginfo_t *info, void *uctx)
{
}
函数参数:
1. signum:信号编号。
2. siginfo_t *:信号详细信息。内部包含:
si_code:信号产生的原因。
si_pid:发送信号的进程ID。
si_uid:发送信号的用户ID。
si_addr:某些信号(如SIGSEGV、SIGBUS),指向发生错误的内存地址。
3. *uctx:参数上下文指针。
作用:
保存信号发生时CPU寄存器状态,包括程序计数器PC、SP、通用寄存器等。
通常为传参为NULL,即不需要上下文。
struct sigaction中成员 sa_flags 典型值:
1. SA_SIGINFO:
表示使用 siginfo_t 接收更多信号信息。
此时使用struct sigaction的sa_sigaction为信号处理函数,而不是sa_handler。
2. SA_RESTART:
无SA_RESTART标志(默认):
当系统调用被信号中断,系统调用将返回EINTR。
此时程序需要检查返回值,若为EINTR则重新执行系统调用。
有SA_RESTART标志:
当处理完中断信号后,被中断的系统调用将会自动重启。
而不返回 EINTR 错误。无需手动重新执行系统调用。
3. SA_NOCLDSTOP:
表示子进程收到暂停信号SIGSTOP时,不向父进程发送SIGCHLD信号。
(通常子进程终止(不论正常,异常)时,会向父进程发送 SIGCHLD 信号)
4. SA_NOCLDWAIT:
表示对子进程的退出状态不感兴趣,子进程终止后,其资源立即释放,不成为僵尸进程。
信号的实现
待决信号(Pending Signal):已将信号发送给进程,但尚未被该进程处理的信号。
struct task_struct {
struct signal_struct *struct;
struct sighand_struct *sighand;
sigset_t blocked; //位掩码,表示该进程阻塞的信号。
struct sigpending pending;
unsigned long sas_ss_sp; //专用信号处理栈的基地址。
size_t sas_ss_size; //专用信号处理栈的大小。
}
成员介绍:
1. struct signal_struct *struct;
该进程的信号描述符。
包含:
信号的默认行为、信号计数器等。
通常同一进程组中的进程共享同一信号描述符。
2. struct sighand_struct *sighand
包含:
不同信号的信号处理函数。
通常父子进程会共享同一该结构,表示使用相同信号处理函数。
struct sighand_struct {
atomic_t count; //共享该结构的进程数。
struct k_sigaction action[_NSIG]; //数组,包含每个信号对应的处理函数。
}
struct k_sigaction {
struct sigaction sa;
};
sa.sa_handler = SIG_DFL; //使用该信号对应默认处理函数。
不同信号,默认的处理函数不同,默认处理函数有:
1. 忽略。
2. 结束进程。
3. 停止(设为TASK_STOPPED)。
4. coredump。
3. struct sigpending pending;
该进程待处理的信号集合(即收到的未阻塞的信号)。
当进程重新获得CPU运行时,查看该集合并处理其中信号。
struct sigpending {
struct list_head list;
//连接所有struct sigqueue实例,sigqueue包含待决信号详细信息。
sigset_t signal; //表示待决信号的位掩码。
};
struct sigqueue {
siginfo_t info; //存储待决信号的详细信息。如下
}
typedef struct siginfo {
int si_signo; //信号编号。
int si_errno; //错误编号(仅当si_code为SIGILL、SIGFPE、SIGSEGV、SIGBUS时有效)。
int si_code; //信号代码。
pid_t si_pid; //发送信号的进程ID。
uid_t si_uid; //发送信号的用户ID。
int si_status;
//子进程的退出状态(仅当si_code为SIGCHLD时有效)
clock_t si_utime;
//进程的用户CPU时间(仅当si_code为SIGCHLD时有效)
clock_t si_stime;
//进程的系统CPU时间(仅当si_code为SIGCHLD时有效)
void *si_addr;
//发生段错误的地址(仅当si_code为SIGSEGV/SIGBUS/SIGILL/SIGFPE时有效)
int si_fd;
//产生信号的文件描述符(仅当si_code为SIGIO、SIGPOLL时有效)
void *si_call_addr;
//发生硬件错误的地址(仅当si_code为SIGTRAP时有效)
int si_syscall;
//发生系统调用错误的系统调用编号(仅当si_code为SIGSYS时有效)
unsigned int si_arch;
// 发生硬件错误的CPU体系架构(仅当si_code为SIGBUS、SIGFPE、SIGILL、SIGSEGV时有效)
} siginfo_t;
通常信号处理函数使用进程的用户栈,也可用sigaltstack函数设置专用栈。
sigaltstack系统调用:
作用:
设置一个线程的备用信号栈。
使用场景:
1. 备用信号栈可处理栈溢出,如错误报告、资源释放等。
2. 确保信号处理函数不影响主调用栈或其他线程。
3. 某些信号处理函数需要大量栈空间,如SIGSEGV段错误。
信号相关系统调用
注意:不是shell命令
1. kill:
向进程组的所有进程发送一个信号。
2. sigpending:
检查是否有待决信号。
5.4.2 管道和套接字
管道:
举例:ls | grep wo
实现: 使用虚拟文件系统对象。
fork/clone进程时,也将复制管道。所以父子进程可通过管道通信。
无名管道:
创建函数:
int pipe(int pipefd[2]);
使用对象:
父子进程间通信。半双工。
文件系统中没有对应无名管道文件。
使用方法:
1. pipe()。返回两个文件描述符(一个用于读,一个写)
2. fork()
3. 父进程读,子进程写(反之亦可)
有名管道:
创建函数:int mkfifo(const char *pathname, mode_t mode)
使用对象:任意进程间通信。
文件系统中存在有名管道文件。
int mkfifo(const char *pathname, mode_t mode)
pathname:需要创建管道文件的文件名。
mode:管道的权限。
后续可基于该pathname文件进行open(), read(), write()等实现进程间通信。