标签:
异常控制流ECF:即这些突变。
关于ECF:
1.ECF是操作系统用来实现I/O、进程和虚拟存器的基本机制
2.应用程序通过使用一个叫做陷阱或者系统调用的ECF形式,向操作系统请求服务
3.ECF是计算机系统中实现并发的基本机制
4.软件异常机制——C++和Java有try,catch,和throw,C中非本地跳转是setjmp和longjmp
异常是异常控制流的一种形式,由硬件和操作系统实现。简单来说,就是控制流中的突变。
出现异常的处理方式:
1.处理器检测到有异常发生
2.通过异常表,进行间接过程调用,到达异常处理程序
3.完成处理后:①返回给当前指令②返回给下一条指令③终止
需要知道几个概念:异常号,异常表,异常表基址寄存器。
关系:
异常号是到异常表中的索引,异常表的起始地址放在异常表基址寄存器。
异常类似于过程调用,区别在:
1.处理器压入栈的返回地址,是当前指令地址或者下一条指令地址。
2.处理器也把一些额外的处理器状态压到栈里
3.如果控制一个用户程序到内核,所有项目都压到内核栈里。
4.异常处理程序运行在内核模式下,对所有的系统资源都有完全的访问权限。
一共有256种不同的异常类型。
每一个系统调用都有一个唯一的整数号,对应于一个到内核中跳转表的偏移量。
系统调用的实现方法:
在IA32中,系统调用通过一条陷阱指令提供:
int n;//n为异常号
所有的到Linux系统调用的参数都是通过寄存器传递的。惯例如下:
进程的经典定义:一个执行中的程序的实例。
系统中的每个程序都是运行在某个进程的上下文中的。
※上下文:由程序正确运行所需的状态组成的。
进程提供给应用程序的关键抽象:
一个私有的地址空间:独占的使用存储器系统
一系列的程序计数器PC的值,分别唯一的对应于包含子啊程序的可执行目标文件中的指令,或者是包含在运行时动态链接到程序的共享对象中的指令,这个PC值的序列就叫做逻辑控制流。
参见图8-12,关键在于:
进程是轮流使用处理器的。每个进程执行它的流的一部分,然后被抢占,然后轮到其他进程。但是进程可以向每个程序提供一种假象,好像它在独占的使用处理器。
异常处理程序、进程、信号处理程序、线程、Java进程
一个逻辑流的执行在时间上与另一个流重叠。【与是否在同一处理器无关】
这两个流并发的运行。
两个流并发的运行在不同的处理机核或者计算机上。
并行流并行的运行,并行的执行。
进程为程序提供的假象,好像它独占的使用系统地址空间。一般而言,和这个空间中某个地址相关联的那个存储器字节是不能被其他进程读写的。
简单的说,用户模式和内核模式的区别就在于用户的权限上,权限指的是对系统资源使用的权限。
具体的区别是有无模式位,有的话就是内核模式,可以执行指令集中的所有指令,访问系统中任何存储器位置;没有就是用户模式。
进程从用户模式变为内核模式的唯一方法是通过异常——中断,故障,或者陷入系统调用。
Linux的聪明机制——/proc文件系统,将许多内核数据结构的内容输出为一个用户程序可以读的文本文件的层次结构。
但我在虚拟机中输入告诉我权限不够?
这个通过最后一节得知,是我输入方法不对,应该输入的是cat打印指令,再接后面的目录,如下图:
操作系统内核使用上下文切换这种较高层形式的异常控制流来实现多任务。上下文切换机制建立在较底层异常机制之上。
由一些对象的值组成:
操作系统讲过。
1.保存当前进程的上下文
2.恢复某个先前被抢占的进程被保存的上下文
3.将控制传递给这个新恢复的进程。
这一节主要是附录A的内容的重复解释,在上周已经学习过。
简单总结就是,系统会使用错误处理包装函数,系统级函数是小写,他们的包装函数名大写,包装函数调用基本函数,有任何问题就终止,如果没有问题和基本函数是一样的。
需要注意的就是,检查错误的思想!!!
每个进程都有一个唯一的正数进程ID(PID)。
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void); 返回调用进程的PID
pid_t getppid(void); 返回父进程的PID(创建调用进程的进程)
终止:永远停止。原因:
1.收到信号,默认行为为终止进程
2.从主程序返回
3.调用exit函数
父进程通过调用fork函数来创建一个新的运行子进程。fork函数定义如下:
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
fork函数只被调用一次,但是会返回两次:父进程返回子进程的PID,子进程返回0.如果失败返回-1.
在图8-15的代码示例为:
/* $begin fork */
#include "csapp.h"
int main()
{
pid_t pid;
int x = 1;
pid = Fork(); //line:ecf:forkreturn
if (pid == 0) { /* Child */
printf("child : x=%d\n", ++x); //line:ecf:childprint
exit(0);
}
/* Parent */
printf("parent: x=%d\n", --x); //line:ecf:parentprint
exit(0);
}
/* $end fork */
相同和不同:
相同:用户栈、本地变量值、堆、全局变量值、代码
不同:私有地址空间
共享文件:子进程继承了父进程所有的打开文件。参考10.6节笔记。
调用fork函数n次,产生2的n次方个进程。
用exit函数。
#include <stdlib.h>
void exit(int status);
exit函数以status退出状态来终止进程。
进程终止后还要被父进程回收,否则处于僵死状态。
如果父进程没有来得及回收,内核会安排init进程来回收他们。init进程的PID为1.
一个进程可以通过调用waitpid函数来等待它的子进程终止或停止。waitpid函数的定义如下:
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
成功返回子进程PID,如果WNOHANG,返回0,其他错误返回-1.
设置为常量WNOHANG和WUNTRACED的各种组合:
在wait.h头文件中定义了解释status参数的几个宏:
如果调用进程没有子进程,那么waitpid返回-1,并且设置errno为ECHILD。
如果waitpid被一个信号中断,那么他返回-1,并且设置errno为EINTR。
wait函数是waitpid函数的简单版本,wait(&status)等价于waitpid(-1,&status,0).
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
成功返回子进程pid,出错返回-1
sleep函数使一个进程挂起一段指定的时间。定义如下:
#include <unistd.h>
unsigned int sleep(unsigned int secs);
返回值是剩下还要休眠的秒数,如果到了返回0.
#include <unistd.h>
int pause(void);
让调用函数休眠,直到该进程收到一个信号。
#include <unistd.h>
int execve(const char *filename, const char *argv[], const char *envp[]);
成功不返回,失败返回-1.
execve函数调用一次,从不返回。
#include <stdlib.h>
char *getenv(const char *name);
若存在则为指向name的指针,无匹配是null
在环境数组中搜寻字符串"name=value",如果找到了就返回一个指向value的指针,否则返回null。
#include <stdlib.h>
int setenv(const char *name, const char *newvalue, int overwrite);
若成功返回0,错误返回-1
void unsetenv(const char *name);
无返回值
如果环境数组包含"name=oldvalue"的字符串,unsetenv会删除它,setenv会用newvalue代替oldvalue,只有在overwrite非零时成立。
如果name不存在,setenv会将"name=newvalue"写进数组。
※fork函数和execve函数的区别
fork函数是创建新的子进程,是父进程的复制体,在新的子进程中运行相同的程序,父进程和子进程有相同的文件表,但是不同的PID
execve函数在当前进程的上下文中加载并运行一个新的程序,会覆盖当前进程的地址空间,但是没有创建一个新进程,有相同的PID,继承文件描述符。
Unix信号:更高层的软件形式的异常允许进程中断其他进程。
传递一个信号到目的进程的两个步骤:发送信号和接收信号。
1.内核检测到一个系统事件
2.一个进程调用了kill函数,显式的要求内核发送一个信号给目的进程。
一个进程可以发送信号给它自己。
1.忽略
2.终止
3.执行信号处理程序,捕获信号
/bin/kill程序可以向另外的进程发送任意的信号,格式是:
/bin/kill -n m
n是信号,m是进程或进程组
当n>0时,发送信号n到进程m
当n<0时,使信号|n|发送到进程组m中的所有进程。
这部分主要看书吧,不重复打了,注意图8-27,理解进程组PID是取自作业中父进程的一个
还有一个抽象概念——作业:为对一个命令行求值而创建的进程。
进程通过调用kill函数发送信号给其他进程。
具体同本节2.
进程可以通过调用alarm函数向它自己发送SIGALRM信号。
#include <unistd.h>
unsigned int alarm(unsigned int secs);
返回前一次闹钟剩余的秒数,若没有返回0.
具体的控制例子参见509页图8-29.
===========对不起老师我实在看不懂书了……我去看视频了……===========
c语言中变量的声明:
声明符
是数组,数组里的元素是指针
int *daytab[13]
是指针,指向一个类型和元素个数都固定的数组
int (*daytab1)[13]
是函数,返回值类型是指针
int *comp()
是指针,指向函数的指针,函数名就是函数指针
int (*comp1)()
每次分析不要跨过括号,位于右边的小括号和中括号具有更高的优先级。
1.数组一定要告诉元素个数和数据类型
2.函数一定有形参和返回值类型
3.数组指针,函数指针,*和指针要括起来。
分析:
typedef char (f)();
typedef f g[];
x可以简化为g x(),所以x是一个返回值指向g类型的指针的函数。
g是一个指针数组,数组内的元素都是指针,指针的类型是一个返回值为char的函数指针。
简化
简化为char (*A)[5],这是一个数组指针,指向一个有五个char型元素的数组
A是(*x[3])(),相当于(*B)(),这是一个函数指针,指向函数B
B是x[3],是一个数组。
Linux进程间通信
信号不携带任何数据,只是作为通知某进程发生了什么事件。
信号是一种进程间通信的方法,应用于异步事件的处理,实质是软中断。
在软件层面。
或者 man 7 signal
每个信号都有一个编号和宏定义名称。具体表见上面。
信号产生、信号注册、信号注销、信号处理
stty-a,查看哪些按键可以产生信号
捕捉:signal函数
忽略信号:SIG_IGN
默认操作:SIG_DFL
处理方法:
1.递归,调用同一个处理函数
2.忽略第二个信号
3.阻塞第二个信号直至第一个处理完毕
c语言中,用户级的异常控制流形式,通过setjmp和longjmp函数提供。
setjump函数在env缓冲区中保存当前调用环境,以供后面longjmp使用,并返回0.
调用环境:程序计数器,栈指针,通用目的寄存器
longjmp函数从env缓冲区中恢复调用环境,然后触发一个从最近一次初始化env的setjmp调用的返回。然后setjmp返回,并带有非零的返回值retval。
注:
setjmp函数只被调用一次,但返回多次;
longjmp函数被调用一次,但从不返回。
STRACE:打印一个正在运行的程序和他的子程序调用的每个系统调用的痕迹
PS:列出当前系统中的进程,包括僵死进程
TOP:打印出关于当前进程资源使用的信息
PMAP:显示进程的存储器映射
参考资料:
1.《深入理解计算机系统》
2.闫佳欣博客:http://www.cnblogs.com/20135202yjx/p/4985230.html
标签:
原文地址:http://www.cnblogs.com/lv-20135229/p/4985551.html