20145205 《信息安全系统设计基础》第十一周学习总结

20145205 《信息安全系统设计基础》第周学习总结

教材学习内容总结

第八章 异常控制流

平滑:指在存储器中指令都是相邻的。
突变:出现不相邻,通常由诸如跳转、调用、和返回等指令造成。
异常控制流ECF:即这些突变。

关于ECF:
1.ECF是操作系统用来实现I/O、进程和虚拟存器的基本机制
2.应用程序通过使用一个叫做陷阱或者系统调用的ECF形式,向操作系统请求服务
3.ECF是计算机系统中实现并发的基本机制
4.软件异常机制——C++和Java有try,catch,和throw,C中非本地跳转是setjmp和longjmp

第一节 异常

异常是异常控制流的一种形式,由硬件和操作系统实现。简单来说,就是控制流中的突变。

事件:即状态变化,与当前指令的执行可能直接相关,也可能没有关系。
出现异常的处理方式:
1.处理器检测到有异常发生
2.通过异常表,进行间接过程调用,到达异常处理程序
3.完成处理后:①返回给当前指令②返回给下一条指令③终止

1.异常处理

需要知道几个概念:异常号,异常表,异常表基址寄存器。

异常号:系统为每种类型的异常分配的唯一的非负整数。
异常表:系统启动时操作系统就会初始化一张条转变,使得条目k包含异常k的处理程序的地址。
关系:
异常号是到异常表中的索引,异常表的起始地址放在异常表基址寄存器。

异常类似于过程调用,区别在:

 1.处理器压入栈的返回地址,是当前指令地址或者下一条指令地址。
 2.处理器也把一些额外的处理器状态压到栈里
 3.如果控制一个用户程序到内核,所有项目都压到内核栈里。
 4.异常处理程序运行在内核模式下,对所有的系统资源都有完全的访问权限。

2.异常的类别

故障指令:执行当前指令导致异常
中断处理程序:硬件中断的异常处理程序。

(1)中断

异步发生
来自处理器外部的I/O设备的信号的结果
返回下一条指令

(2)陷阱

陷阱是有意的异常
是执行一条指令的结果
最重要的用途——系统调用

(3)故障

由错误状况引起,可能能够被故障处理程序修正
结果要么重新执行指令(就是返回当前指令地址),要么终止
典型示例:缺页异常

(4)终止

是不可恢复的致命错误造成的结果
通常是一些硬件错误

3.Linux/IA32系统中的异常

一共有256种不同的异常类型。

(1)Linux/IA32故障和终止

除法错误/浮点异常 异常0 终止程序
一般保护故障/段故障 异常13 终止程序
缺页 异常14 返回当前地址
机器检查 异常18 终止程序

(2)Linux/IA32系统调用

每一个系统调用都有一个唯一的整数号,对应于一个到内核中跳转表的偏移量。

系统调用的实现方法:
在IA32中,系统调用通过一条陷阱指令提供:

int n;//n为异常号
所有的到Linux系统调用的参数都是通过寄存器传递的。惯例如下:

%eax:包含系统调用号
%ebx,%ecx,%edx,%esi,%edi,%ebp:包含最多六个任意参数
%esp:栈指针,不能使用

第二节 进程

进程的经典定义:一个执行中的程序的实例。

系统中的每个程序都是运行在某个进程的上下文中的。

※上下文:由程序正确运行所需的状态组成的。

进程提供给应用程序的关键抽象:

一个独立的逻辑控制流:独占的使用处理器
一个私有的地址空间:独占的使用存储器系统

1.逻辑控制流

(1)含义

一系列的程序计数器PC的值,分别唯一的对应于包含子啊程序的可执行目标文件中的指令,或者是包含在运行时动态链接到程序的共享对象中的指令,这个PC值的序列就叫做逻辑控制流。

(2)

参见图8-12,关键在于:

进程是轮流使用处理器的。每个进程执行它的流的一部分,然后被抢占,然后轮到其他进程。但是进程可以向每个程序提供一种假象,好像它在独占的使用处理器。

(3)逻辑流示例

异常处理程序、进程、信号处理程序、线程、Java进程

2.并发流

(1)含义

一个逻辑流的执行在时间上与另一个流重叠。【与是否在同一处理器无关】

这两个流并发的运行。

(2)几个概念

并发:多个流并发的执行
多任务:一个进程和其他进程轮流运行(也叫时间分片)
时间片:一个进程执行它的控制流的一部分的每一时间段
(3)并行

两个流并发的运行在不同的处理机核或者计算机上。

并行流并行的运行,并行的执行。

3.私有地址空间

进程为程序提供的假象,好像它独占的使用系统地址空间。一般而言,和这个空间中某个地址相关联的那个存储器字节是不能被其他进程读写的。

4.用户模式和内核模式

简单的说,用户模式和内核模式的区别就在于用户的权限上,权限指的是对系统资源使用的权限。

具体的区别是有无模式位,有的话就是内核模式,可以执行指令集中的所有指令,访问系统中任何存储器位置;没有就是用户模式。

进程从用户模式变为内核模式的唯一方法是通过异常——中断,故障,或者陷入系统调用。

Linux的聪明机制——/proc文件系统,将许多内核数据结构的内容输出为一个用户程序可以读的文本文件的层次结构。

但我在虚拟机中输入告诉我权限不够?

这个通过最后一节得知,是我输入方法不对,应该输入的是cat打印指令,再接后面的目录,如下图:

5.上下文切换

操作系统内核使用上下文切换这种较高层形式的异常控制流来实现多任务。上下文切换机制建立在较底层异常机制之上。

(1)上下文:内核重新启动一个被抢占的进程所需的状态。

由一些对象的值组成:

通用目的寄存器
浮点寄存器
程序计数器
用户栈
状态寄存器
内核栈
内核数据结构:页表、进程表、文件表
(2)调度和调度器

操作系统讲过。

(3)上下文切换机制

1.保存当前进程的上下文

2.恢复某个先前被抢占的进程被保存的上下文

3.将控制传递给这个新恢复的进程。

(4)可能发生上下文切换的原因:

内核代表用户执行系统调用时
中断
第三节 系统调用错误处理

这一节主要是附录A的内容的重复解释,在上周已经学习过。

简单总结就是,系统会使用错误处理包装函数,系统级函数是小写,他们的包装函数名大写,包装函数调用基本函数,有任何问题就终止,如果没有问题和基本函数是一样的。

需要注意的就是,检查错误的思想!!!

第四节 进程控制

一、获取进程ID

每个进程都有一个唯一的正数进程ID(PID)。

#include <sys/types.h>
#include <unistd.h>

pid_t getpid(void); 返回调用进程的PID
pid_t getppid(void); 返回父进程的PID(创建调用进程的进程)

二、创建和终止进程

1.进程总是处于下面三种状态之一

运行
停止:被挂起且不会被调度
终止:永远停止。原因:

1.收到信号,默认行为为终止进程
2.从主程序返回
3.调用exit函数
2.创建进程

父进程通过调用fork函数来创建一个新的运行子进程。fork函数定义如下:

#include <sys/types.h>
#include <unistd.h>

pid_t fork(void)
fork函数只被调用一次,但是会返回两次:父进程返回子进程的PID,子进程返回0.如果失败返回-1.

代码示例为:

/* $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次方个进程。

3.终止进程

用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.

1.判断等待集合的成员——pid

pid>0:等待集合是一个单独子进程,进程ID等于pid
pid=-1:等待集合是由父进程所有的子进程组成
其他。
2.修改默认行为——options

设置为常量WNOHANGWUNTRACED的各种组合:

3.检查已回收子进程的退出状态——status

在wait.h头文件中定义了解释status参数的几个宏:

WIFEXITED如果子进程通过调用exit或一个返回正常终止,就返回真
WEXITSTATUS:返回一个正常终止的子进程的退出状态。只有在WIFEXITED返回为真时,才会定义这个状态
WIFSIGNALED:如果子进程是因为一个未被捕获的信号终止的,那么返回真
WTERMSIG:返回导致子进程终止的信号的编号。只有在WIFSIGNALED返回为真时才定义这个状态
WIFSTOPPED:如果引起返回的子进程当前是被停止的,那么返回真
WSTOPSIG:返回引起子进程停止的信号的数量。只有在WIFSTOPPED返回为真时才定义这个状态

4.错误条件

如果调用进程没有子进程,那么waitpid返回-1,并且设置errno为ECHILD。

如果```waitpid``被一个信号中断,那么他返回-1,并且设置errno为EINTR。

5.wait函数

wait函数是waitpid函数的简单版本,wait(&status)等价于waitpid(-1,&status,0).

include <sys/types.h>
include <sys/wait.h>

pid_t wait(int *status);

成功返回子进程pid,出错返回-1

四、让进程休眠

1.sleep函数

sleep函数使一个进程挂起一段指定的时间。定义如下:

include <unistd.h>

unsigned int sleep(unsigned int secs);

返回值是剩下还要休眠的秒数,如果到了返回0.

2.pause函数

include <unistd.h>

int pause(void);

让调用函数休眠,直到该进程收到一个信号。

五、加载并运行程序——execve函数

include <unistd.h>

int execve(const char *filename, const char *argv[], const char *envp[]);

成功不返回,失败返回-1.
execve函数调用一次,从不返回。

filename:可执行目标文件
argv:参数列表
envp:环境列表

新程序开始时:

getnev函数


char *getenv(const char *name);

若存在则为指向name的指针,无匹配是null
在环境数组中搜寻字符串"name=value",如果找到了就返回一个指向value的指针,否则返回null。

setenvunsetenv函数


int setenv(const char *name, const char *newvalue, int overwrite);

若成功返回0,错误返回-1

void unsetenv(const char *name);

无返回值

fork函数和execve函数的区别

fork函数是创建新的子进程,是父进程的复制体,在新的子进程中运行相同的程序,父进程和子进程有相同的文件表,但是不同的PID

execve函数在当前进程的上下文中加载并运行一个新的程序,会覆盖当前进程的地址空间,但是没有创建一个新进程,有相同的PID,继承文件描述符。

第五节 信号

Lunix信号:更高层的软件形式的异常允许进程中断其他进程。

一、信号术语

传递一个信号到目的进程的两个步骤:发送信号和接收信号。

发送信号的原因:

1.内核检测到一个系统事件
2.一个进程调用了kill函数,显式的要求内核发送一个信号给目的进程。
一个进程可以发送信号给它自己。

接收信号:

1.忽略
2.终止
3.执行信号处理程序,捕获信号
待处理信号:

只发出没有被接收的信号
任何时刻,一种类型至多只会有一个待处理信号,多的会被直接丢弃
一个进程可以选择性的阻塞接受某种信号,被阻塞仍可以被发送,但是不会被接收
一个待处理信号最多只能被接收一次。
pending:待处理信号集合
blocked:被阻塞信号集合。

二、发送信号——基于进程组

1.进程组

每个进程都只属于一个进程组。
进程组ID:正整数
一个子进程和他的父进程属于同一进程组。
查看进程组id:getpgrp
修改进程组:setpgid

2.用kill函数发送信号

进程通过调用kill函数发送信号给其他进程。

具体同本节2.(这里有个关于KILL贴子,是我运行了sigdemo2后真的听不下来,上网查到的帖子,虽没用上,但是是一种新的方法,类似于我们平时使用的任务管理器)

5.用alarm函数发送信号

进程可以通过调用alarm函数向它自己发送SIGALRM信号。


unsigned int alarm(unsigned int secs);

返回前一次闹钟剩余的秒数,若没有返回0.
具体的控制例子参见509页图8-29.

本周代码分析

首先进行集中编译



之后开始进行代码的运行和分析

关于psh

 psh1.c 代码功能:输入要执行的指令,回车表示输入结束,然后输入的每个参数对应到函数中,再调用对应的指令,在执行了一次后会自动退出

 psh2.c 代码功能:比psh1多了循环执行功能,可以多次使用,不主动退出是无法退出的。

关于forkdemo

 forkdemo1.c代码:先是打印进程pid,然后调用fork函数生成子进程,休眠一秒后再次打印进程id,这时父进程打印子进程pid,子进程返回0
 forkdemo2.c代码:调用两次fork,一共产生四个子进程,所以会打印四个aftre输出
 forkdemo3.c代码:fork产生子进程,父进程返回子进程pid,不为0,所以输出父进程的那句话,子进程返回0,所以会输出子进程中的printf。
 forkdemo4.c代码:先打印进程pid,然后fork创建子进程,父进程返回子进程pid,所以输出父进程一句,休眠十秒;子进程返回0,所以输出子进程与之后一句

关于pipe

 pipe单独运行时会提示我们在运行pipe时后面跟着两个命令,但是cmd1和cmd2之间空格隔开,而且这个前后顺序还有关系,cmd2高于cmd1

关于pipedemo

 pipedemo.c:展示了如何创建管道并使用管道来向自己发送数据
 pipedemo2.c:说明了如何将pipe和fork结合起来,创建一对通过管道来通信的进程。在程序中显示了从键盘到进程,从进程到管道,再从管道到进程以及从进程回        到终端的数据传输流

关于testbuf

 testbuf1.c和testbuf2.c代码运行结果一致,因为fflush(stdout)的效果和换行符\n是一样的
 testbuf3.c:输出当前进程pid和当前进程的父进程的pid

关于testpid

 testpid.c:输出当前进程pid和当前进程的父进程的pid

关于waitdemo

 waitdemo1.c:功能是如果有子进程,则终止子进程,成功返回子进程pid。
 waitdemo2.c:比waitdemo1.c来就是多了一个子进程的状态区分,把状态拆分成三块,exit,sig和core

关于sigdemo

 sigdemo1.c:程序连续输出五个hello,中间等待时间为2秒且在此期间输入的Ctrl+C都被处理成打印OUCH,就像是进程被强关就是在挨打一样,十分有趣
 sigdemo2.c:程序开始就表明态度,是关不掉他的,一直输出haha,没有什么办法,我在博客上文页也提到了使用kill关闭进程,具体做法在我发出的帖子链接中。
 sigdemo3.c:在read函数不发生错误的情况下输入什么,就输出什么,输入的Ctrl+C也无法终止程序,只有输入quit的时候才会退出


关于listargs

 listargs.c:证明shell并不将重定向标记和文件名传递给程序

本周代码托管截图


行数的统计截图

学习进度条

代码行数(新增/累积) 博客量(新增/累积) 学习时间(新增/累积) 重要成长
目标 5000行 24篇 350小时
第一周 0/0 1/2 8/8
第二周 49/49 1/3 10/18
第三周 0/49 2/5 10/28
第四周 0/49 0/5 0/28
第五周 42/91 1/6 15/43
第六周 184/275 1/7 30/73
第七周 94/369 1/8 20/93
第八周 0/369 2/10 15/105
第九周 215/584 1/11 20/125
第十周 560/1144 2/13 20/145
第十一周 918/2062 2/15 30/175

参考资料

posted @ 2016-11-27 22:12  20145205武钰  阅读(150)  评论(2编辑  收藏  举报