异常控制流

异常

  • 异常
    最简单的处理器控制流是平滑的,即指令按顺序执行。但是系统必须对状态变化进行反应,这种变化不一定和程序执行相关,如一个硬件定时器定期产生信号。异常就是控制流中的突变,用来响应处理器状态中的某些变化。
    这里写图片描述

  • 异常处理
    当处理器检测到有事件发生时,就会通过异常表进行跳转,调用异常处理程序。异常表的起始地址放在一个叫做异常表基址寄存器的特殊CPU寄存器里。
    这里写图片描述

  • 异常类别
    这里写图片描述

    • 中断
      中断是来自处理器外部的I/O设备的信号的结果。I/O设备通过向处理器芯片上的一个引脚发信号,并将异常号放到系统总线上,在当前指令完成执行之后,处理器注意到中断引脚的电压变高了,就从系统总线读取异常号,然后调用适当的中断处理程序。
      这里的异步,个人觉得意为与当前的指令无关,而同步的异常时执行当前指令的结果。
    • 陷阱
      陷阱通过系统调用,向内核请求服务。处理器提供的“syscall n”指令会导致一个到异常处理程序的陷阱。
    • 故障
      故障由错误引起,它可能能够被处理程序修正,如果处理程序能够修正这个错误,就会将控制返回到引起错误的指令并重新执行它。
    • 终止
      终止是不可恢复的错误,应用程序会被终止。

    系统调用错误处理:当Unix系统级函数遇到错误时,它们典型地会返回-1,并设置全局整数变量errno来表示什么出错了。

  • 异常示例
    这里写图片描述
    其中,0~31的号码对应的是由Intel架构师定义的异常。32~255的号码对应的是操作系统定义的中断和陷阱。接下来我们来看下系统调用。
    这里写图片描述
    Linux提供上百种系统调用,每个系统调用都有一个唯一的整数号,对应于一个到内核中跳转表的偏移量。

  • 进程

    进程的经典定义就是一个执行中的程序的实例。系统中的每个程序都是运行在某个进程的上下文(context)中的。上下文是程序运行的状态。

    • 私有地址空间
      一个进程为每个程序提供它自己的私有地址空间。

      进程和程序:程序是一堆代码和数据;进程是执行中程序的一个具体实例。程序总是运行在某个进程的上下文中。例:fork函数在新的子进程中运行相同的程序;execve函数在当前进程上下文中加载运行一个新的程序,它会覆盖当前进程的地址空间,但并没有创建新的进程。

    • 用户模式和内核模式
      处理器通过模式位来控制进程权限。内核模式下,进程可以执行指令集中的任何指令,并且可以访问系统中任何存储器位置。用户模式中的进程不允许执行特权指令,如停止处理器、改变模式位,或者发起一个读操作。也不允许用户模式中的进程直接引用地址空间中内核区内的代码和数据。用户程序必须通过系统调用接口间接地访问内核代码和数据。运行应用程序代码的进程初始时是在用户模式中的。进程从用户模式变为内核模式的唯一方法是通过诸如中断、故障或者陷人系统调用这样的异常。

    • 上下文切换
      操作系统内核通过上下文切换来实现多任务。
      这里写代码片

    进程控制

    • 获取进程ID
      每个进程都有一个唯一的正数进程ID(PID)。
      getpid()函数返回调用进程的PID。
      getppid()函数返回它的父进程的PID(创建调用进程的进程)。
    • 进程组
      每个进程都只属于一个进程组,进程组是由一个正整数进程组ID来标识的。默认地,一个子进程和它的父进程同属于一个进程组。
      getpgrp()函数返回当前进程的进程组ID。
      setpgid(pid_t pid,pid_t pgid)函数改变进程的进程组。
    • 创建进程fork
      fork()函数会创建一个子进程,新创建的子进程与父进程几乎完全相同,最大的区别在于有不同的PID。
      父进程与子进程是并发运行的独立进程。
      fork函数会返回两次,在父进程中返回子进程的PID,在子进程中返回0。
    • 回收子进程waitpid
      当一个进程由于某种原因终止时,内核并不是立即把它从系统中清除。而是被保持在一种已终止的状态中,直到被它的父进程回收。一个终止了但还未被回收的进程称为僵死进程。如果父进程没有回收它的僵死子进程就终止了,那么内核就会安排init进程来回收它们。
      我们使用waitpid(pid_t pid,int *status,int options)或wait(int *status)来回收子进程。
    • 进程休眠sleep
      sleep(unsigned int secs)函数将一个进程挂起一段指定的时间。
    • 加载并运行程序execve
      execve(const char *filename, const char *argv[], const char *envp[])函数在当前进程的上下文中加载并运行一个新程序。如果成功则不返回,失败返回-1。

    信号

    一个信号就是一条小消息,它通知进程系统中发生了一个某种类型的事件。
    这里写图片描述
    一个只发出而没有被接收的信号叫做待处理信号。在任何时刻,一种类型至多只会有一个待处理信号,多余的会被丢弃。一个进程可以有选择性地阻塞接收某种信号,当一种信号被阻塞时,它仍可以被发送,但是产生的待处理信号不会被接收,直到进程取消对这种信号的阻塞。即,同一个信号最多保持两个,一个在处理中,一个待处理。
    这里写图片描述

    • 信号产生
      内核通过更新目的进程上下文中的某个状态,发送一个信号给目的进程。发送信号可以有如下两个原因:
      1)内核检测到一个系统事件,比如被零除错误或者子进程终止。
      2) 一个进程调用了kill函数,显式地要求内核发送一个信号给目的进程。

      • 用kill程序发送信号
        /bin/kill程序可以向其它进程发送任意的信号。

        unix> /bin/kill -9 15123 //发送信号9给PID为15123的进程
        unix> /bin/kill -9 -1530 //发送信号9给PID为1530的进程组中每个子进程
      • 从键盘发送信号
        在键盘上输人ctrl-c会导致一个SIGDINT信号被发送到外壳,外壳捕获该信号后发送SIGDINT信号到这个前台进程组中的每个进程。类似地,输人ctrl-z会发送一个SIGTSTP信号到外壳。

      • 用kill函数发送信号
        同kill程序类似,kill(int pid,int sig)可以发送信号给其他进程(包括自己)。
      • 用alarm函数发送信号
        进程可以通过调用alarm(unsigned int secs)函数向它自己发送SIGALRM信号。
    • 信号处理
      当内核从一个异常处理程序返回,它会检查进程的未被阻塞的待处理信号的集合。如果集合非空,则强制接受一个信号(通常是最小的),收到这个信号会触发进程的某种行为。
      通过signal(int signum,sighandler_t handler)可以修改信号的默认行为。signum是信号的序号,handler是自定义的信号处理程序。

      因为信号处理的一些特性,比如待处理信号的阻塞,只能保留一个待处理信号,信号调用可以被中断,信号处理并发等原因,会导致一些问题,书中有做探讨,这里就不做记录了。

    非本地跳转

    非本地跳转将控制直接从一个函数转移到另一个函数,而不需要经过正常的调用一返回序列。
    setjmp(jmp_buf env)函数保存当前的执行状态,并且返回0。
    longjmp(sigjmp_buf env, int savesigs)函数返回到setjmp的位置,并且设置一个返回值。
    setjmp相当于设置了一个锚点,比如错误处理程序,当发生错误的时候,就可以通过longjmp直接跳转到错误处理程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值