APUE-Learning-标准IO & 信号
标准I/O
流与FILE对象
- 注意与struct file[打开文件表项]结构的不同
- 一个字符可以使用单个(ASCII字符集)或多个(国际字符集)字节表示
- 多字节I/O函数 (wchar.h)
- 标准I/O打开文件时,创建了一个流对象,并将其与该文件关联起来
- fopen()打开一个流时,返回一个指向FILE对象的指针
- FILE对象中包含详尽的信息:用于实际I/O的文件描述符、指向用于该流缓冲区的指针、缓冲区的长度、当前在缓冲区中的字符数、出错标志等
- 预定义了三个标准流,使用<stdio.h>中定义的FILE指针,stdin, stdout, stderr
- 流和文件的概念是分开的:流即使用FILE表示,文件只能与某个流对应起来
缓冲类型
- 全缓冲:填满标准I/O缓冲区后,才进行实际的I/O操作。驻留在磁盘上的文件通常由标准I/O实施全缓冲。在一个流上执行第一次I/O操作时,相关标准I/O函数通常调用malloc函数获得需使用的缓冲区
- 也可调用fflush(file flush)将未填满的缓冲区写到磁盘上
- 行缓冲:当在输入/输出中遇到换行符时【由于缓冲区长度固定,当缓冲区满时也会执行实际I/O操作】,标准I/O库执行I/O操作。标准输入/输出通常为行缓冲
- 不带缓冲:标准错误流通常不带缓冲
二进制I/O
- 指定读写的数据类型长度,及数据个数
进程流程控制
setjump(jmp_buf env)
- 调用该宏时将当前上下文环境保存再env中,包括栈指针、PC、寄存器等,以便后续longjump使用
- 如果该宏直接返回则它的返回值为0
- 如果它从后续的longjump()返回,则返回一个非零值
longjump()
- 使用env中保存的前面某个流程的环境,跳转到该执行流
信号 -> 异步场景解决方案
- 不存在编号为0的信号,kill函数对编号0有特殊的应用
信号的处理
初始化时,可以告诉内核在某个信号发生时按下列三种方式之一进行处理:
- 忽略此信号:SIGKILL和SIGSTOP信号是不能被忽略的。实际应用编程时可以设置忽略吗? -> 不可以
- 捕捉信号:某种信号发生时,内核调用用户提前注册的回调函数。无法捕捉SIGKILL和SIGSTOP信号
- 默认动作:大多数信号的默认动作是终止进程
早期的不可靠信号
- 进程每次接收到信号对其进行处理时,随即将该信号动作重置为默认值
- 只得在处理完逻辑后重新设置该信号的信号处理函数
- 然而在接收处理到重新设置这段时间内,有可能新的信号产生
- 并执行默认动作(有可能是终止程序)
可重入函数
- 在信号处理函数中调用不可重入函数,其结果是不可预知的
- 信号处理函数可以运行在用户态,打断之前执行的用户进程
可靠信号
信号产生 -> 这段时间内信号是未决(pending)的 ->内核在进程结构体中设置标志位(向进程递送信号)
信号阻塞:进程可以设置阻塞某信号,如果该信号的处理方式为默认或者捕获时;当该信号产生时,内核将保持该信号为pending状态,直至进程解除了对该信号的阻塞,或者忽略该信号。于是,进程在信号传递给它之前仍可改变对该信号的动作
- 如果解除阻塞之前,该信号产生了多次,如何处理?
- POSIX允许内核递送该信号一次或多次(排队,且需支持POSIX.1实时扩展)
- 此外,内核可能已经有了新的变化
- 不能阻塞SIGKILL和SIGSTOP信号
- 如果解除阻塞之前,该信号产生了多次,如何处理?
如果同时有多个待递送的信号,如何处理?
- POSIX.1没有规定递送顺序,但是建议:先递交与进程当前状态有关的信号,如SIGSEGV
信号屏蔽字:每个进程都有一个信号屏蔽字,每一位与一个具体的信号规定。使用数据类型:
sigset_t信号集:
sigset_t,可以表示多个信号sigaction
- sa_mask:在调用通过sigaction()注册的信号处理函数之前,要将sa_mask声明的信号屏蔽字加到进程的信号屏蔽字中;仅当从该信号处理函数返回时再恢复进程的信号屏蔽字
- sa_flags:
- SA_INTERRUPT:由此信号中断的系统调用不自动重启
- SA_RESTART:此信号中断的系统调用自动重启
- SA_SIGINFO:信号处理函数多了两个形参
sigsetjump/siglongjump -> POSIX规范定义
- 在使用sigaction,当执行其注册的信号处理函数时,进程的信号屏蔽字可能已经被修改,如果此时在信号处理函数中使用longjump()改变执行流,那么进程的信号屏蔽字不会恢复
- 因此,提供了新的宏用于信号处理的情况下使用
- 从这里也可以看出,信号处理函数应该是工作在用户态的,否则不能使用longjump跳转回用户程序
信号阻塞与pause
- pasue系统调用会挂起当前进程,直到某个信号产生、被处理并返回,之后该进程被唤醒
- 考虑:某信号被阻塞,之后想要等待该信号的发生。因此要先解除阻塞,并且使用pause()
- 如果该信号在阻塞期间产生,则其可能在解除阻塞后立即被递送给进程,导致后续的pause()不再被唤醒
- 为了解决这个问题,需要原子的修改进程信号屏蔽字并进入等待
- sigsuspend()函数完成这个原子操作
APUE-Learning-标准IO & 信号
http://example.com/2024/07/22/APUE-Learning-标准IO-信号/