20145223《信息安全系统设计基础》第11周学习总结
20145223 《信息安全系统设计基础》第11周学习总结
代码学习内容总结
exec1.c
·exec1.c代码如下:
·代码中可以看出调用了execvp这个函数,execvp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名,找到后便执行该文件,然后将第二个参数argv传给该欲执行的文件
·如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中
·运行结果如下:
·结果分析:exevp函数调用成功没有返回,所以没有打印出“* * * ls is done. bye”这句话
exec2.c
·代码如下:
·可以看出它与exec1的区别就在于exevp函数的第一个参数,exec1传的是ls,exec2直接用的arglist[0]
·运行结果如下:
·结果分析:ls 和arglist[0]等价,运行结果是相同的。
exec3.c
·代码如下:
·运行结果如下:
·结果分析:execlp()会从PATH 环境变量所指的目录中查找符合参数file的文件名,找到后便执行该文件,然后将第二个以后的参数当做该文件的argv[0]、argv[1]……,最后一个参数必须用空指针(NULL)作结束。如果用常数0来表示一个空指针,则必须将它强制转换为一个字符指针,否则将它解释为整形参数,如果一个整形数的长度与char * 的长度不同,那么exec函数的实际参数就将出错。如果函数调用成功,进程自己的执行代码就会变成加载程序的代码,execlp()后边的代码也就不会执行了.
返回值:如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno 中。也就是说,这个代码指定了环境变量,然后依然执行了ls -l指令,成功后没有返回,所以最后一句话不会输出。运行结果同exec1.
forkdemo1.c
·运行结果:
·代码:
·代码先是打印进程pid,然后调用fork函数生成子进程,sleep一秒后再次打印进程id,这时父进程打印子进程pid,子进程返回0。
forkdemo2.c
·运行结果:
·代码:
·这个代码调用两次fork,一共产生四个子进程,所以会打印四个after
forkdemo3.c
·运行结果:
·代码:
·结果分析:fork函数会将一个进程分成两个进程,并且会返回两次。
forkdemo4.c
·代码:
·运行结果:
·结果分析:先打印进程pid,然后fork创建子进程,父进程返回子进程pid,所以输出parent一句,休眠十秒;子进程返回0,所以输出child与之后一句
forkgdb.c
·代码:
·运行结果:
·结果分析:这个的主要区别是在,父进程打印是先打印两句,然后休眠一秒,然后打印一句,子进程先打印一句,然后休眠一秒,然后打印两句。并且这两个线程是并发的,所以可以看到在一个线程休眠的那一秒,另一个线程在执行,并且线程之间相互独立互不干扰。
psh1.c
·运行结果:
·结果分析:这个代码就相当于你输入要执行的指令,回车表示输入结束,然后输入的每个参数对应到函数中,再调用对应的指令。
psh2.c
·运行结果:
·结果分析:比ps1多了循环判断,,通过父进程,循环输入指令。只要有一个子进程没有结束,父进程就被挂起。所以当wait返回pid时没说明,子进程都已经结束,即用户输入的指令都已经执行完毕。因为execute函数在大的循环中调用,所以会循环执行下去,除非用户强制退出。另外,当子进程正常执行完用户指令后,子进程的状态为0,若执行指令出错,子进程的状态为1。
testbuf1:
·运行结果:
·结果分析:先输出hello,然后保持在循环中不结束进程
testbuf2
·运行结果:
·结果分析:fflush(stdout)的效果和换行符\n是一样的。
testbuf3
·代码:
·运行结果:
·结果分析:将内容格式化输出到标准错误、输出流中。
testpid
·代码:
·运行结果:
·结果分析:输出当前进程pid和当前进程的父进程的pid
testsystem
·代码:
·运行结果:
·结果分析:system()——执行shell命令,也就是向dos发送一条指令。后面可以跟两个参数,然后向dos发送这两个命令,分别执行.
waitdemo1
·代码:
·运行结果:
·结果分析:如果有子进程,则终止子进程,成功返回子进程pid
waitdemo2
·代码:
·运行结果:
·结果分析:比起waitdemo1来就是多了一个子进程的状态区分,把状态拆分成三块,exit,sig和core。
environ.c
·运行结果:
·结果分析:先打印了一开始的初始环境变量,接着重新设置环境变量,并打印输出。
environvar.c
·运行结果:
·结果分析:将外部变量environ的内容打印出来,即打印系统相关宏值。
consumer.c
·代码:
·运行结果:
·结果分析:输出打开文件流的进程号,以及打开文件进程号,并返回打开文件的结果
producer.c
·代码:
·运行结果:
sigdemo1.c
·代码:
·运行结果:
·结果分析:每过两秒输出一次hello,一共输出五次
sigdemo2.c
·代码:
·运行结果:
·结果分析:一直输出haha,按Ctrl+C不能停止,必须按Ctrl+Z。SIG_DFL,SIG_IGN 分别表示无返回值的函数指针,指针值分别是0和1,这两个指针值逻辑上讲是实际程序中不可能出现的函数地址值。
SIG_DFL:默认信号处理程序
SIG_IGN:忽略信号的处理程序
sigdemo3.c
·代码:
·运行结果:
·结果分析:输入消息,在屏幕上打印出来。
sigactdemo.c
·代码:
·运行结果:
·结果分析:
flag
SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL
SA_RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用
SA_NODEFER :一般情况下, 当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置SA_NODEFER标记, 那么在该信号处理函数运行时,内核将不会阻塞该信号
sigaction()会依参数signum指定的信号编号来设置该信号的处理函数。参数signum可以指定SIGKILL和SIGSTOP以外的所有信号。
sigactdemo2.c
·代码:
·运行结果:
·结果分析:每两秒输出一次two seconds passed
pipe.c
·代码:
·运行结果:
·结果分析:管道指令功能
pipedemo.c
·代码:
·运行结果:
pipedemo2.c
·代码:
·运行结果:
stdinredir1.c
·代码:
·运行结果:
·结果分析:输入3行信息,接着分别打印这三行信息,执行打开文件语句,若打开正常,则从文件中读取前三行信息
stdinredir2.c
·代码:
·运行结果:
教材学习内容总结
异常控制流(ECF):
·现代系统通过使控制流发生突变来对系统状态变化做出反应。一般而言的这些突变就称为异常控制流。
ECF是操作系统用来实现I/O、进程和虚拟存储器的基本机制。
应用程序通过使用一个叫做陷阱或者系统调用的ECF形式,向操作系统请求服务。
操作系统为应用程序提供了强大的ECF机制,用来创建新进程、以及终止当前进程。
ECF是计算机系统中实现并发的基本机制。
·异常就是控制流中的突变,用来响应处理器状态中的某些变化。
·根据异常的事件的类型,会发生以下三种情况中的一种:
处理程序将控制返回给当前指令Icurr,即当事件发生时正在执行的命令。
处理程序将控制返回给Inext,即如果没有发生异常将会执行的下一条指令。
处理程序终止被中断的程序。
·在系统启动时(当计算机重启或者加电时),操作系统分配和初始化一张称为异常表的跳转表,使得条目k包含异常k的处理程序的地址。
·在运行时,处理器检测到发生了一个事件,并且确定了相应的异常号k。随后,处理器触发异常,方法是执行间接过程调用,通过一场表的条目k转到相应的处理程序。
·异常表的起始地址放在一个叫做异常表基址寄存器里。
·异常类似于过程调用,但又有一些不同之处:
过程调用时,在跳转到处理程序之前,处理器将返回地址压入栈中。然而,根据异常的类型,返回地址要么是当前指令,要么就是下一条指令。
处理器也把一些额外的处理器状态压到栈里,在处理程序返回时,重新开始被中断的程序会需要这些状态。
如果控制从一个用户程序转移到内核,那么所有这些项目都被压倒内核栈中,而不是压到用户栈中。
异常处理程序运行在内核模式下,这意味着他们对所有的系统资源都有完全的访问权限。
//一旦硬件出发了异常,剩下的工作就是由异常处理程序在软件中完成。
·异常可以分为:
- 中断:异步发生,是来自处理器外部的I/O设备的信号的结果。总是返回到下一条指令。
- 陷阱:同步,来自有意的异常,总是返回到下一条指令。
- 故障:同步,来自潜在可恢复的错误,可能返回到当前指令。
- 终止:同步,来自不可恢复的错误,通常是一些硬件错误;不会返回。
·陷阱最重要的用途是在用户程序是在用户程序和内核之提供一个像过程一样的接口,叫做** 系统调用**。
·每个系统调用都有一个唯一的整数号,对应于一个到内核中跳转表的偏移量。
进程
·进程的经典定义就是一个执行中的程序的实例。 - 进程提供给应用程序的关键抽象:
一个独立的逻辑控制流,它提供一个假象,好像我们的程序独占的使用处理器。
一个私有的地址空间,它提供一个假象,好像我们的程序独占的使用存储器系统。
·如果想用调试器单步执行程序,我们会看到一系列的程序计数器(PC)的值,而这些值,这个PC值的序列叫做逻辑控制流,或者简称逻辑流。
·进程是轮流使用处理器的。每个进程执行它的一部分,然后被抢占(暂时挂起),然后轮到其他进程。
·一个逻辑流的执行在时间上与另一个流重叠,称为并发流,这两个流被称为并发的运行。
·多个流并发的执行的一般现象称为并发;一个进程和其他进程轮流进行的概念成为多任务;一个进程执行它的控制流的一部分的每一时间段叫做时间片。因此,多任务也叫做时间分片。
·注意:并发的思想与流运行的处理器核数或者计算机数无关。
·处理器通常使用某个控制寄存器模式位来提供这种功能的,该寄存器描述了当前进程享有的特权。当设置了模式位时,就进入内核模式(也称超级用户模式),否则,进程就运行在用户模式中。即不允许执行特权指令。也不允许用户模式中的进程直接引用地址空间中内核区内的代码和数据。任何这样的尝试都会导致致命的故障。
·进程从用户模式变为内核模式的唯一方法是通过诸如中断,故障或者陷入系统调用这样的异常。上下文切换 :用它这个较高层形式的异常控制流来实现多任务。
·内核为每个进程维持一个上下文。在进程执行的某些时刻,内核可以决定抢占当前进程。并重新开始一个先前被抢占的进程,这种决定就叫做调度,是由内核中称为调度器的代码处理的。
1):保存当前进程的上下文。
2):恢复某个先前被抢占的进程被保存的上下文。
3):将控制传递给这个新恢复的进程。
系统调用错误处理
错误报告函数:
void unix_error(char *msg)
{
fprintf(stderr,"%s: %s\n", msg, strerror(errno));
exit(0);
}
通过使用错误处理包装函数,我们可以更进一步的简化我们的代码。包装函数调用基本函数,检查错误,如果有问题就终止。比如,下面是fork函数的错误处理包装函数:
pid_t Fork(void)
{
pid_t pid;
if ((pid = fork())< 0)
unix_error("Fork error");
return pid;
}
给定这个包装函数,我们对fork的调用就缩减为1行:
pid = Fork();
- 本周代码托管
代码增长情况
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 3000行 | 20篇 | 200小时 | |
第0周 | 0/0 | 1/1 | 5/5 | |
第1周 | 0/0 | 1/2 | 10/15 | |
第2周 | 300/300 | 1/3 | 15/30 | |
第3周 | 200/500 | 1/4 | 10/40 | |
第5周 | 150/650 | 1/5 | 10/50 | |
第6周 | 50/700 | 1/6 | 8/58 | |
第7周 | 0/700 | 1/7 | 8/64 | |
第8周 | 0/700 | 2/9 | 5/70 | |
第9周 | 181/881 | 2/11 | 7/77 | |
第10周 | 0/881 | 2/11 | 5/52 | |
第11周 | 1017/1898 | 2/13 | 7/59 |