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有特殊的应用

信号的处理

初始化时,可以告诉内核在某个信号发生时按下列三种方式之一进行处理:

  • 忽略此信号:SIGKILLSIGSTOP信号是不能被忽略的。实际应用编程时可以设置忽略吗? -> 不可以
  • 捕捉信号:某种信号发生时,内核调用用户提前注册的回调函数。无法捕捉SIGKILLSIGSTOP信号
  • 默认动作:大多数信号的默认动作是终止进程

早期的不可靠信号

  • 进程每次接收到信号对其进行处理时,随即将该信号动作重置为默认值
    • 只得在处理完逻辑后重新设置该信号的信号处理函数
    • 然而在接收处理到重新设置这段时间内,有可能新的信号产生
    • 并执行默认动作(有可能是终止程序)

可重入函数

  • 在信号处理函数中调用不可重入函数,其结果是不可预知的
  • 信号处理函数可以运行在用户态,打断之前执行的用户进程

可靠信号

  • 信号产生 -> 这段时间内信号是未决(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-信号/
作者
Cyokeo
发布于
2024年7月22日
许可协议