标签:
控制转移
控制流
系统必须能对系统状态的变化做出反应,这些系统状态不是被内部程序变量捕获,也不一定和程序的执行相关。
现代系统通过使控制流 发生突变对这些情况做出反应。我们称这种突变为异常控制流
( Exceptional Control Flow,ECF
)
异常控制流
发生在系统的各个层次。
理解ECF
很重要
ECF
将帮助你理解重要的系统概念。ECF
将帮助你理解应用程序如何与操作系统交互 trap
)或者系统调用(system call
)的ECF形式,向操作系统请求服务。ECF
将帮助你编写有趣的应用程序ECF
将帮助你理解并发ECF
将帮助你理解软件异常如何工作。这一章你将理解如何与操作系统交互,这些交互都围绕ECF
异常是异常控制流的一种,一部分由硬件实现,一部分由操作系统实现。
异常
(exception)就是控制流的突变,用来响应处理器状态的某些变化。状态变化又叫做事件
(event)
通过异常表
(exception table)的跳转表,进行一个间接过程调用,到专门设计处理这种事件的操作系统子程序(异常处理程序
(exception handler))
异常处理完成后,根据事件类型,会有三种情况
为每个异常分配了一个非负的异常号
(exception number)
系统启动时,操作系统分配和初始化一张称为异常表
的跳转表。
异常表
的地址放在叫异常表基址寄存器
的特殊CPU寄存器中。)
异常
类似过程调用
,不过有以下不同
异常分为一下四类:中断
(interrupt),陷阱
(trap),故障
(fault)和终止
(abort)。
中断
中断
是异步发生,是来自处理器外部的I/O设备的信号的结果。硬件中断不是由任何一条专门的指令造成,从这个意义上它是异步的。陷阱和系统调用
陷阱
是有意的异常,是执行一个指令的结果。也会返回到下一跳本来要执行的指令。陷阱
最重要的用途是在用户程序和内核之间提供一个像过程一样的接口,叫做系统调用 syscall n
的指令故障
故障由错误引起,可能被故障处理程序修正。
abort
例程,进行终结。终止
abort
例程。有高达256种不同的异常
23~255 对应操作系统定义的中断和陷阱。
除法错误
机器检查
在IA32系统中,系统调用是通过一条称为int n
的陷阱指令完成,其中n可能是IA32异常表256个条目中任何一个索引,历史中,系统调用是通过异常128(0x80)提供的。
C程序可用syscall
函数来直接调用任何系统调用
研究程序如何使用int指令直接调用Linux 系统调用是很有趣的。所有到Linux系统调用的参数都是通过通用寄存器而不是栈传递。
惯例
- %eax 包含系统调用号
- %ebx,%ecx,%edx,%esi,%edi,%ebp包含六个任意的参数。
- %esp不能使用,进入内核模式后,内核会覆盖它。
系统级函数写的hello world
int main()
{
write(1,"hello,world\n",13);
exit(0);
}
汇编写的hello world
string:
"hello world\n"
main:
movl $4,%eax
movl $1,%ebx
movl $String,%ecx
movl $len,%edx
int $0x80
movl $1,%eax
movl $0,%ebx
int $0x80
异常
是允许操作系统提供进程
的概念的基本构造快,进程
是计算机科学中最深刻,最成功的概念之一。 进程
经典定义:一个执行中的程序实例. 上下文
中的。 进程
提供的假象 逻辑控制流
。地址空间
。逻辑控制流
,或者简称逻辑流
逻辑流
也有不同的形式。
一个逻辑流的执行在执行上与另一个流重叠,称为并发流
,这两个流被称为并发地运行。
多个流并发执行的一般现象称为并发
。
多任务
。时间片
。时间分片
并发
的思想与流运行的处理器核数与计算机数无关。
并行流
。你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持
并发
也不支持并行
。你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持
并发
。你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持
并行
。
并发
的关键是你有处理多个任务的能力,不一定要同时。
并行
的关键是你有同时处理多个任务的能力。
进程
为个程序好像独占了系统地址空间。
进程
为每个程序提供它自己的私有地址空间。处理器提供一种机制,限制一个应用程序可以执行的指令以及它可以访问的地址空间范围。这就是用户模式
和内核模式
。
处理器通过控制寄存器中的一个模式位
来提供这个功能。
模式位
后,进程就运行在内核模式中(有时也叫超级用户模式
) 模式位
时,进程运行在用户模式。 保护故障
。系统调用
间接访问内核代码和数据。Linux提供一种聪明的机制,叫/proc
文件系统。
/proc
文件将许多内核数据结构输出为一个用户程序可以读的文本文件的层次结构。 /proc/cpuinfo
)/sys
文件系统。 在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程。这种决定叫做调度(shedule
),由内核中称为调度器(scheduler
)的代码处理的。
当调度进程时,使用一种上下文切换
的机制来控制转移到新的进程
sleep
系统调用,显式请求让调用进程休眠。高速缓存污染和异常控制流
一般而言,硬件高速缓存存储器不能和诸如中断和上下文切换这样的异常控制流很好地交互,如果当前进程被一个中断暂时中断,那么对于中断处理程序来说高速缓存器是冷的。如果处理程序从主存访问足够多的表项,被中断的进程继续的时候,高速缓存对于它来说也是冷的,我们称中断处理程序污染了高速缓存。使用 上下文切换也会发生类似的现象。
当Unix系统级函数遇到错误时,他们典型地返回-1,并设置全局变量errno
来表示什么出错了。
if((pid=fork()<0){
fprintf(stderr,"fork error: %s\n", strerror(errno));
exit(0);
}
#include<sys/types.h>
#include<unistd.h>
pid_t getpid(void);
pid_t getppid(void);
getpid()
返回调用进程的PID,getppid()
返回它的父进程的PID。pid_t
的值,在Linux系统下在type.h被定义为int进程总是处于下面三种状态
停止。进程的执行被挂起,且不会被调度。
SIGSTOP
,SIGTSTP
,SIDTTIN
或者SIGTTOU
信号,进程就会停止。SIGCONT
信号,在这个时刻,进程再次开始运行。信号
是一种软件中断的形式。终止。进程永远停止。
status退出状态
来终止进程(另一种设置方式在main中return )父进程通过调用fork
函数创建一个新的运行子进程
#include<sys/types.h>
#include<unistd.h>
pid_t fork(void);
返回:子进程返回0,父进程返回子进程的PID,如果出错,返回-1;
新创建的子进程几乎但不完全与父进程相同。
子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份拷贝。
fork()
函数会第一次调用,返回两次,一次在父进程,一次在子进程。
并发执行
相同但是独立的地址空间
共享文件
画进程图会有帮助。
当一个进程由于某种原因终止时,内核并不是立即把它从系统中清除。相反,进程被保持在一种已终结的状态,知道被它的父进程 回收(reap
)。
当父进程回收已终止的子进程时,内核将子进程的退出状态传递给父进程,然后抛弃已终止的进程。
一个终止了但还未被回收的进程叫做僵死进程
如果父进程没有回收,而终止了,那么内核安排init
进程来回收它们。
init
进程的的PID位1,在系统初始化时由内核创建的。一个进程可以通过调用waitpid函数来等待它的子进程终止或停止
#include<sys/types.h>
#include<sys/wait.h>
pid_t waitpid(pid_t pid ,int *status, int options);
返回:如果成功,则为子进程的PID,如果WNOHANG,则为0,如果其他错误,则为-1.
waitpid
函数有点复杂。默认(option=0
)时,waitpid
挂起调用进程的执行,知道它的等待集合中的一个子进程终止,如果等待集合的一个子进程在调用时刻就已经终止,那么waitpid
立即返回。在这两种情况下,waitpid
返回导致waitpid
返回的已终止子进程的PID
,并且将这个已终止的子进程从系统中去除。
判断等待集合的成员
等待集合的成员通过参数pid确定
pid>0
,那么等待集合就是一个独立的子进程,它的进程ID
等于PID
pid=-1
,那么等待集合就是由父进程所有的子进程组成的。waitpid
函数还支持其他类型的等待集合,包括UNIX进程组等,不做讨论。修改默认行为(此处书中有问题,作用写反了)
可以通过将options
设置为常量WHOHANG
和WUNTRACED
的各种组合,修改默认行为。
PID
·检查已回收子进程的退出状态
标签:
原文地址:http://blog.csdn.net/zy691357966/article/details/51371360