掌握管道和I/O重定向:pipe, dup, dup2
(ECF:Exceptional Control Flow)在计算机系统中的意义:
ECF 是OS用来实现 I/O(异步、同步IO)、进程(进程切换,多任务系统支持)和虚拟存储器(VSS)的基本机制。
APP通过使用一种叫做陷阱(trap)或者系统调用(System call)的ECF,向OS请求服务。
为APP交互提供了更多渠道:信号
为编程语言提供了软件层面的异常处理机制。软件异常允许程序进行非本地跳转(违法通常的调用、返回栈规则的跳转)来响应错误情况。
进程
现代计算机通过时钟信号让CPU进行运作,通过时间片的理念将OS进行分时处理,允许OS多进程工作。
在完成这个业务的时候,OS需要进行上下文切换:状态保存、状态恢复。
另外在进行系统调用和陷阱处理时,OS要完成用户态和内核态的转换控制。
在系统启动时,OS分配的初始化一张称为异常表的跳转表,使得表目k包含异常k的处理程序的地址。
异常处理程序运行在内核模式下,这意味着它们对所的资源都有完全的访问权限。
在任何情况下,当处理器检测到有事件发生时,他就会通过异常表进行跳转,执行异常处理程序。
异常
处理器在检测到事件(CPU的状态被编码为不同的位和信号)发生时,通过异常表进行一个间接过程调用,到一个专门处理这类事件的OS子系统(异常处理程序)。
类别:
类别 原因 异步/同步 返回行为
中断 来自IO设备的信号 异步 总是返回到下一条指令
陷阱 有意的异常 同步 总是返回到下一条指令
故障 潜在可恢复的错误 同步 可能返回当前指令
终止 不可恢复的错误 同步 不会返回
中断是异步发生的,是来自处理器外部的I/O设备的信号的结果。硬件中断不是由任何一条专门的指令造成的,从这个意义上来说,它是异步的,而其他异常时同步发生的,是执行当前指令的结果。
陷阱是有意的异常,是执行一条指令的结果。陷阱最重要的用途是在用户程序和内核之间提供一个向过程一样的接口。
故障由错误情况引起,它可能能够被故障处理程序修正。如果错误能够修正,它就将控制返回到引起故障的命令,否则将返回到内核中的abort例程,终止引起故障的应用程序。
终止是不可恢复的致命错误造成的影响,通常是硬件错误。终止从不将控制返回给应用程序。
异常是允许操作系统提供进程的概念所需要的基本结构快。进程的经典定义就是一个执行中的程序的实例。系统中的每个程序都是运行在某个进程的上下文中,上下文由程序正确运行所需的状态组成,状态包括存放在存储器中的程序代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
一个进程为每个程序提供它自己的私有地址空间,一般来说,和这个空间中的某个地址相关联的那个存储器字节是不能被其他进程读或者写的,从这个意义上说,这个地址空间是私有的。
##linux系统调用
在Linux中,可以通过fork函数新建一个子进程,子进程得到与父进程用户级虚拟地址空间相同的但是相互独立的一份拷贝,包括文本、数据、bss段、堆以及用户栈。子进程还获得与父进程任何打开文件描述符相同的拷贝,这意味着当父进程调用fork时,子进程可以读写父进程打开的任何文件。在父进程中fork函数返回子进程的pid值,而在子进程中fork函数返回0,因此fork函数调用一次却返回两次(一次在父进程,一次在子进程)。
当一个进程由于某种原因终止时,内核并不是立即把它从系统中清除掉,相反,进程被保持在僵死状态,必须由父进程回收。如果父进程还没有回收僵死子进程就终止了,内核会安排init进程来回收它们。即使僵死进程没有运行,他们仍然消耗系统的存储器资源。
逻辑控制流,并发流,私有地址任务,用户模式和内核模式,上下文切换
UNixfork函数的调用检查错误:
if((pid = fork())<0){
fprintf(stderr,"fork erroe: %s\n",streeor(error));
exit(0);
}
strerror 函数返回一个文本串,定义错误报告函数
void unix_error(char *msg)
{
fprintf(stderr,"%s: %s\n",msg,strerror(error));
exit(0);
}
fork调用的简化:
if((pid = fork())<0)
Unix_error("fork error");
fork函数的错误处理包装函数:
pid_t Fork(void)
{
pid_t pid;
if((pid = fork())<0)
unix_error("Fork error ");
return pid;
}
进程总处于下面三种状态
运行,停止,终止
父进程通过调用fork函数创建一个新的运行子子进程
#include
#include
pid_t fork(void);
调用一次,返回两次。
并发执行
相同的但是独立的地址空间
回收子进程
调用waitpid函数来等它的子进程终止或停止
pid的判定
如果pid>0,那么等待集合就是一个单独的子进程,ID等于pid。
如果pid=-1,那么等待就是由父进程所有的子进程。
让进程休眠
sleep
#include
undigned int sleep(unsigned int secs);
exevce 函数在当前进程的上下文加载并运行一个新的程序、
#inclde
int exevce(const char* filename, const char argv[])
const char envp)'
发送信号两个原因:
内核检测到一个系统事件,比如被零除错误或子进程的终止。、
一个进程调用kill函数
1.进程组
•每个进程都只属于一个进程组。
•进程组ID:正整数
•一个子进程和他的父进程属于同一进程组。
•查看进程组id:getpgrp
•修改进程组:setpgid
2.用/bin/kill程序发送信号
/bin/kill程序可以向另外的进程发送任意的信号,格式是:
/bin/kill -n m
n是信号,m是进程或进程组
当n>0时,发送信号n到进程m
当n<0时,使信号|n|发送到进程组m中的所有进程。
3.从键盘发送信号
这部分主要看书吧,不重复打了,注意图8-27,理解进程组PID是取自作业中父进程的一个
还有一个抽象概念——作业:为对一个命令行求值而创建的进程。
4.用kill函数发送信号
进程通过调用kill函数发送信号给其他进程。
具体同本节2.
5.用alarm函数发送信号
进程可以通过调用alarm函数向它自己发送SIGALRM信号。
unsigned int alarm(unsigned int secs);
返回前一次闹钟剩余的秒数,若没有返回0.
声明
c语言中变量的声明:
•类型
•声明符
指针数组
是数组,数组里的元素是指针
int *daytab[13]
数组指针
是指针,指向一个类型和元素个数都固定的数组
int (*daytab1)[13]
指针函数
是函数,返回值类型是指针
int *comp()
函数指针
是指针,指向函数的指针,函数名就是函数指针
int (*comp1)()
一个信号就是一条小消息,它通知进程系统中发生了一个某种类型的事件。一种信号类型对应于一种系统事件,内核通过更新目的进程上下文中的某个状态,来表明发送一个信号给目的进程。当目的进程被内核强迫以某种方式对信号的发送作出反应时,目的进程就接收了信号。进程可以选择忽略、终止或者执行一个信号处理程序来捕获信号。
一种类型的信号最多只能有一个被捕获、一个待处理。如果有一个信号待处理,则相同类型的其他信号会被忽略(抛弃)。
在某些系统中,当处理程序捕获到一个信号时,被中断的慢速系统调用将会不再继续,而是立刻返回给用户一个错误条件。
C语言提供了一种用户级异常控制流形式,成为非本地跳转,可以控制直接从一个函数转移到另一个当前正在执行的函数,而不经过正常的调用-返回序列。非本地跳转通过setjmp和longjmp函数实现。setjmp函数在参数缓冲区(env)中保存当前的调用环境,以供后面longjmp使用,并立刻返回0。longjmp函数从env中恢复调用环境,然后从最近一次初始化env的setjmp函数调用处返回。setjmp函数调用一次,但返回多次(每次调用longjmp函数时返回),而longjmp函数调用一次却不返回(从setjmp函数调用处返回)。C++和Java中的异常机制是较高层次的setjmp和longjmp函数的结构化版本。