20144303《信息安全系统设计基础》第十一周学习总结
代码部分学习
forkdemo1:
运行结果:
第一次输出Before,输出的是父进程的pid。接下来调用fork,且将fork的返回值(父进程的返回值是子进程的pid,子进程的返回值是0)赋值给ret_form_fork。
然后输出两次After。第一个After是父进程的输出,输出父进程的pid为3927,输出fork的返回值(子进程的pid)为3928。 第二个After是子进程的输出,输出子进程的pid3928,fork的返回值0
同时,多次运行这个程序发现每次输出的pid都不相同,证明每次运行时系统赋予进程的pid都不同
forkdemo2:
运行结果:
输出一次Before,输出的是父进程的pid
输出四次After,第一次输出父进程的pid,后面三次输出子进程的pid
同时,根据输出的pid顺序交叉,也可以看出这些进程是并发执行的
forkdemo3:
运行结果:
fork的返回值:父进程返回子进程的pid,子进程返回0,错误返回-1
Before输出父进程的pid
将fork的返回值赋值给fork_rv,进行if判断
执行父进程时,fork_rv不是0也不是-1,所以输出i am parent,同时输出子进程的pid
执行子进程时,fork_rv是0,输出i am parent,同时输出自己的pid
forkdemo4:
forkdemo4和forkdemo3大致相同,只要知道getppid函数的功能是获得父进程的pid就能读懂程序
运行结果:
forkgdb:
(把sleep的时间改大一些会便于观察)
父子进程是并发执行的,当遇到sleep时,会结束当前进程,执行另一个进程。
所以,先输出parent的li0、gi0,遇到sleep,转到运行子进程,输出child的li0,遇到sleep,转到运行父进程,结束sleep后,输出父进程的si0、li1、gi1,又遇到sleep。循环往复,直到全部输出。
运行结果:
exec1:
函数说明:
execvp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名,找到后便执行该文件,然后将第二个参数argv传给该欲执行的文件。
返回值:
如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中。
execvp()搜索的PATH环境变量中指定的目录中的ls命令的位置,而传递参数的ls命令在arglist中
第一个参数arglist[0]代表它要执行文件的位置, 第二个参数arglist[1]是命令
运行结果:
将函数中的参数修改:
运行结果:
没有输出“* * * ls is done. bye”是因为execvp函数调用成功,没有返回
exec2:
exec1中,execvp=(“ls”,arglist)
,execvp函数直接调用“ls”。
exec2中,execvp=(arglist[0],arglist)
,execvp函数调用arglist[0],而arglist[0]中存放的是ls,所以输出相同。
运行结果:
exec3:
execv、execlp、execvp、execvpe四个函数都能实现相同的功能。
函数execv():
int execv(const char *path, char *const argv[]);
execv会停止执行当前的进程,并且以被执行的应用程序替换被停止执行的进程,进程ID没有改变。
argv: 传递给应用程序的参数列表, 注意,这个数组的第一个参数应该是应用程序名字本身,并且最后一个参数应该为NULL,不参将多个参数合并为一个参数放入数组。
函数execlp():
int execlp(const char *filename, const char arg0, ... / (char *)0 */ );
execlp()函数会从PATH环境变量所指的目录中查找文件名为第一个参数指示的字符串,找到后执行该文件,第二个及以后的参数代表执行文件时传递的参数列表,最后一个参数必须是空指针.
函数execvp():
int execvp(const char *file, char *const argv[]);
execvp()函数会从PATH环境变量所指的目录中查找文件名为第一个参数指示的字符串,找到后执行该文件,第二个及以后的参数代表执行文件时传递的参数列表,最后一个成员必须是空指针.
运行结果:
psh1:
与exec的功能大致相同,psh1能实现参数的输入,可以执行任意命令。
运行结果:
psh2:
在psh1的基础上加了循环,可以多次输入。
运行结果:
testbuf1:
在printf()后使用fflush(stdout)的作用是立刻将要输出的内容输出。
当使用printf()函数后,系统将内容存入输出缓冲区,等到时间片轮转到系统的输出程序时,将其输出。
使用fflush(out)后,立刻清空输出缓冲区,并把缓冲区内容输出。
while(1)是一个死循环,只能强制退出
运行结果:
testbuf2:
运行结果:
testbuf3:
fprintf是用于文件操作的
printf(...)等价于fprintf(stdout,...)
stdout:标准输出设备
stderr:标准错误输出设备
strerr是作为程序运行过程中的错误显示出来的,默认情况下,标准输入从键盘读取,同时标准输出和标准错误会打印到屏幕。
运行结果:
testpid:
运行结果:
输出进程的pid和父进程的pid
testpp:
malloc 向系统申请分配指定size个字节的内存空间。返回类型是 void* 类型。
如果分配成功则返回指向被分配内存的指针(此存储区中的初始值不确定),否则返回空指针NULL。当内存不再使用时,应使用free()函数将内存块释放。函数返回的指针一定要适当对齐,使其可以用于任何数据对象。
运行结果:
提示出现段错误,可能是以下情况:
- 访问系统数据区,尤其是往 系统保护的内存地址写数据 最常见就是给一个指针以0地址
- 内存越界(数组越界,变量类型不一致等
- 访问到不属于你的内存区域
testsystem:
运行结果:
waitdemo1:
wait(NULL)作用是等待子进程退出。NULL的意思是退出状态不关注。
先输出mypid,然后父进程,到wait语句时转换到运行子进程,输出“child pid here”,然后sleep,到exit(17)时,子进程退出,运行父进程,输出“done waiting for 子进程pid”
运行结果:
waitdemo2:
在waitdemo1的基础上,增加了输出子进程的exit、sig、core
运行结果:
教材学习内容总结
8.1 异常
异常就是控制流中的突变,用来响应处理器状态中的某些变化
8.1.1 异常处理
- 系统中可能的每种类型的异常都分配了一个唯一的非负整数的异常号。
- 处理器:被零除、缺页、存储器访问违例、断点以及算术溢出
- 操作系统:系统调用和来自外部I/O设备的信号
8.1.2异常的类别
- 中断
- 来自I/O设备的信号,异步,总是返回到下一条指令
- 陷阱
- 有意的异常,同步,总是返回到下一条指令
- 故障
- 潜在可恢复的错误,同步,可能返回到当前指令
- 终止
- 不可恢复的错误,同步,不会返回
8.1.3 Linux系统中的异常
Linux故障和终止
- 除法错误:异常号:0,通常报告为“浮点异常”
- 一般保护故障:异常号:13,Linux不会尝试回复这类故障,通常报告为“段故障”
- 缺页:异常号:14,是会重新执行产生故障的指令的一个异常事例
- 机器检查:异常号:18,从不返回控制给应用程序
Linux系统调用
每个系统调用都有一个唯一的整数号,对应于一个到内核中跳转表的偏移量
将系统调用和与他们相关的包装函数称为系统级函数
8.2 进程
系统中的每个程序都是运行在某个进程的上下文中的。上下文是由程序正确运行所需的状态组成的
8.2.1 逻辑控制流
- 程序计数器(PC)的值的序列叫做逻辑控制流,简称逻辑流
- 每个进程执行它的流的一部分,然后被抢占(暂时挂起),然后轮到其他进程
8.2.2 并发流
- 一个逻辑流的执行在时间上与另一个流重叠,称为并发流
- 并发:多个流并发的执行的一般现象
- 多任务:一个进程和其他进程轮流运行
- 时间片:一个进程执行它的控制流的一部分的每一段时间
8.2.3 私有地址空间
这个空间中某个地址相关联的那个存储器字节是不能被其他进程读或者写的
8.2.4 用户模式和内核模式
- 当设置了模式位时,进程就运行在内核模式中
- 没有设置模式位时,进程就运行在用户模式中。用户模式中的进程不允许执行特权指令。
- 进程从用户模式变位内核模式的唯一方法是通过诸如中断、故障或者陷入系统调用这样的异常。
- /proc文件系统允许用户模式进程访问内核数据结构的内容
8.2.5 上下文切换
- 上下文切换:是较高形式的异常控制流来实现多任务
- 调度:在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程。这种决定叫做调度
- 当内核代表用户执行系统调用时,可能会发生上下文切换。如果系统调用因为等待某个事件发生阻塞,那么内核可以让当前进程休眠,切换到另一个进程
- 中断也可能引发上下文切换
8.3 系统调用错误处理
- 定义错误报告函数,简化代码
- 错误处理包装函数
8.4 进程控制
8.4.1获取进程ID
- 每个进程都有一个唯一的正数进程ID
- gitpid函数返回调用进程的PID,getppid函数返回它的父进程的PID(创建调用进程的进程)
8.4.2 创建和终止进程
- 进程状态:运行、停止、终止
- exit函数以status退出状态来终止进程
- fork函数
- 父进程通过调用fork函数创建一个新的运行子进程
- 当父进程调用fork时,子进程可以读写父进程中打开的任何文件
- 父进程和子进程之间最大的区别在于有不同的PID
- 调用一次,返回两次
- 并发执行
- 相同但是独立的地址空间
- 共享文件
8.4.3 回收子进程
- 僵死进程没有运行,他们仍然消耗系统的存储器资源
- 一个进程可以通过调用waitpid函数来等待它的子进程终止或停止
8.4.4 让进程休眠
- sleep函数将一个进程挂起一段指定的时间
- pause函数让调用函数休眠,直到该进程收到一个信号
8.4.5 加载并运行程序
- execve函数在当前进程的上下文中加载并运行一个新程序,execve调用一次并从不返回
- argv参数列表,envp环境变量
- getenv函数
8.5 信号
- Unix信号:更高层的软件形式的异常,允许进程中断其他进程
- P505:30种不同类型的信号表
8.5.1 信号术语
- 发送信号:1)内核检测到一个系统事件 2)一个进程调用了kill函数
- 接收信号:当目的进程被内核强迫以某种方式对发送的信号作出反应时,目的进程就接收了信号
- 待处理信号:一个只发出而没有被接收的信号
8.5.2 发送信号
进程组
每个进程都只属于一个进程组
一个进程可以通过使用setpigd函数来改变自己或者其他进程的进程组
用/bin/kill程序发送信号
一个为负的PID会导致信号被发送到进程组PID中的每个进程中
8.5.3 接收信号
如果集合是非空的,那么内核选择集合中的某个信号k,并且强制p接收信号k
signal函数可以改变和信号signum相关联的行为
8.5.4 信号处理问题
捕获多个信号时的问题:
- 待处理信号被阻塞
- 待处理信号不会排队等待
- 系统调用可以被中断
8.5.5 可移植的信号处理
- 不同系统之间,信号处理语义有差异
- sigaction函数明确地指定它们想要的信号处理语义
8.5.6 显式地阻塞和取消阻塞
- 应用程序可以使用sigprocmask函数显式地阻塞和取消阻塞选择的信号
- sigprocmask函数改变当前已阻塞信号的集合
8.6非本地跳转
- 通过setjmp和longjmp函数来提供
- setjmp函数只被调用一次,但返回多次:一次是当第一次调用setjmp,而调用环境保存在缓冲区env中时,一次是为每个相应的longjmp调用
- longjmp只调用一次,但从不返回
- 非本地跳转的一个重要应用就是允许从一个深层嵌套的函数调用中立即返回,通常是由检测到某个错误情况引起的
- 非本地跳转的另一个重要应用是使一个信号处理程序分支到一个特殊的代码位置,而不是返回到达中断了的指令位置
8.7 操作进程的工具
Linux系统提供监控和操作进程的工具:打印一个正在运行的程序和它的子进程调用的每个系统调用的轨迹(STRACE)、列出当前系统中包括僵死进程的进程(PS)、打印出关于当前进程资源使用的信息(TOP)、显示进程的存储器映射(PMAP)、虚拟文件系统(/proc)
课后作业中的问题及解决
习题8.3:
刚开始看着道题的时候,认为有4种输出序列:abcc、bacc、acbc、bcac。但是答案上只写了3个,没有bcac。后来才发现,是忽略了waitpid函数。因为有waitpid,要等待子进程结束时,才会执行父进程的打印c命令。bcac是父进程都结束了,a进程还没有开始,所以不会有这种输出顺序。
习题8.4:
这道题应该注意!pid是逻辑非运算,只会输出0或1。
另外就是注意父进程执行到waitpid函数时,要等子进程结束了才能继续下一条指令
WIFEXITED,如果子进程通过调用exit或一个return正常终止,就返回真值,所以当子进程正常结束的时候,会执行printf(“%d ”,WEXITSTATUS(status))
WEXITSTATUS返回一个正常终止的子进程的退出状态。这段代码中子进程的退出状态是2,所以会输出2
代码托管
链接:https://git.oschina.net/20144303sys/work
感悟(其他)
本周的学习的主要内容是课本第八章以及老师在群里发的代码。在开始研究代码之前,我先把课本第八章的知识认真学习了一遍。在对课本上内容有一定的掌握之后,开始看代码,就感觉相对容易了一些。过程中遇到了一些不认识的函数和参数,也在网上也找到了答案。虽然这周学习量有点多,但收获也不少。希望自己能保持学习的势头,再接再厉。
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 0/0 | 1/1 | 20/20 | |
第二周 | 300/300 | 1/2 | 20/40 | |
第三周 | 300/600 | 1/3 | 20/60 | |
第五周 | 200/800 | 2/5 | 20/80 | |
第六周 | 100/900 | 2/7 | 20/80 | |
第七周 | 160/1060 | 1/8 | 20/100 | |
第八周 | 0/1060 | 2/9 | 20/120 | |
第九周 | 300/1360 | 2/11 | 20/140 | |
第十周 | 495/1855 | 2/13 | 20/160 | |
第十一周 | 1000/2855 | 2/15 | 20/180 |