20145335郝昊 《信息安全系统设计基础》第11周学习总结

20145335郝昊 《信息安全系统设计基础》第11周学习总结

代码实践内容总结

  • exec1

    代码中使用了execvp函数,其中定义函数int execvp(const char file ,char const argv []);execvp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名,找到后便执行该文件,然后将第二个参数argv传给该欲执行的文件。

    即如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中。

    运行结果:

    exevp函数调用成功没有返回,所以没有打印“***ls is done.bye”

  • exec2

    和exec1的区别在于函数exevp的第一个参数,exec1传的是ls,exec2直接使用arglist[0]。

    运行结果与exec1相同

  • exec3

    使用了execlp函数,函数execlp()会从PATH 环境变量所指的目录中查找符合参数file的文件名,找到后便执行该文件,然后将第二个以后的参数当做该文件的argv[0]、argv[1]……,最后一个参数必须用空指针(NULL)作结束。如果用常数0来表示一个空指针,则必须将它强制转换为一个字符指针,否则将它解释为整形参数,如果一个整形数的长度与char * 的长度不同,那么exec函数的实际参数就将出错。如果函数调用成功,进程自己的执行代码就会变成加载程序的代码,execlp()后边的代码也就不会执行了.

    返回值:如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno 中。

    运行结果:

    注意:这个代码指定了环境变量,然后依然执行了ls -l指令,成功后没返回,所以最后一句不会输出,结果同前两个相同

  • forkdemo1

    根据看书上p494的例子就知道了fork()函数调用一次,返回两次,一次是返回到父进程,另一次是回到新创建的子进程。调用fork函数生成子进程,休眠一秒后再次打印进程id,这时父进程打印子进程pid,子进程返回0.

    运行结果:

  • forkdemo2

    根据fork()函数的特点,一次调用两次返回。一共会产生四个子进程,会打印四个after输出。

    运行结果:

  • forkdemo3

    观察代码知道函数()fork产生子进程,父进程返回子进程pid,不为0,所以输出父进程的那句话,子进程返回0,所以会输出子进程那句话。

    运行结果:

  • forkdemo4

    先打印进程pid,然后fork创建子进程,父进程返回子进程pid,所以输出parent一句,休眠十秒;子进程返回0,所以输出child与之后一句。

    运行结果:

  • forkgdb

    这个代码示例是逐步执行fork函数。并与之前的区别主要在于,父进程打印是先打印两句,然后休眠一秒,然后打印一句,子进程先打印一句,然后休眠一秒,然后打印两句。并且这两个线程是并发的,所以可以看到在一个线程休眠的那一秒,另一个线程在执行,并且线程之间相互独立互不干扰。

    运行结果:

  • psh1

    这个代码的功能输入要执行的指令,回车表示输入结束,然后输入的每个参数对应到函数中,再调用对应的指令。

    运行结果:

  • psh2

    这个的代码相比起1,多了循环判断,不退出会一直需要输入指令,并且对于子程序存在的状态条件。

    运行结果:

  • testbuf1

    程序效果是输出hello,然后换行。其中fflush(studout);的作用是强制马上输出,避免错误。

    运行结果:

  • testbuf2

    程序效果是输出hello

    运行结果:

  • testbuf3

    函数fprintf()将内容格式化输出到标准错误、输出流中。

    运行结果:

  • testpid

    输出当前进程pid和当前进程的父进程的pid,其中函数getpid()用来取得目前进程识别码,此值来建立临时文件,以避免临时文件相同带来问题。

    运行结果:

  • testpp

    遇见到了问题段错误

    运行结果:

  • testsystem

    system()——执行shell命令,也就是向dos发送一条指令。这里是后面可以跟两个参数,然后向dos发送这两个命令,分别执行。

    运行结果:
    

  • waitdemo1:

    这段代码使用了sleep()函数、getpid函数()、exit()函数,fork()函数。如果有子进程,则终止子进程,成功返回子进程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。

  • getpid函数返回调用进程的PID,getppid函数返回它的父进程的PID。上面两个函数返回一个同类型为pid_t的整数值,在linux系统中,它在types.h中被定义为int。

8.4.2 创建和终止进程
  • 进程总处于三种状态

(1)运行:进程要么在CPU上执行,要么在等待被执行且最终会被内核调度。

(2)停止:程序的执行被挂起,,且不会被调度。

(3)终止:进程用永远停止了。

  • 终止原因:

(1)收到一个信号,默认行为是终止进程

(2)从主进程返回

(3)调用exit函数

  • 父进程通过调用fork函数创建一个新的运行的子进程。

  • 子进程和父进程的异同:

    • 异:有不同的PID

    • 同:用户级虚拟地址空间,包括:文本、数据和bss段、堆以及用户栈。任何打开文件描述符,子进程可以读写父进程中打开的任何文件。

  • fork函数: 因为父进程的PID总是非零的,返回值就提供一个明确的方法来分辨程序是在父进程还是在子进程中执行。

  • fork函数的特点:

(1)调用一次,返回两次

(2)并发执行

(3)相同的但是独立的地址空间

(4)共享文件

8.4.3 回收子进程
  • 当父进程回收已终止的子进程时,内核将子进程的退出状态传递给父进程,然后抛弃已终止的进程。
    一个终止了但还未被回收的进程称为僵死进程。

  • 一个进程可以通过调用waitpid函数来等待它的子进程终止或者停止。

      #include <sys/types.h>
      #include <sys/wait.h>
    
      pid_t waitpid(pid_t pid,int *status,int options);
      //返回:若成功,返回子进程的PID;若WNOHANG,返回0;若其他错误,返回-1。
    

默认地,当option=0时,waitpid挂起调用进程的执行,直到它的等待集合中的一个子进程终止。

  • 判定等待集合的成员

    有参数pid来确定的:

    (1)pid>0:等待集合是一个单独的子进程,进程ID等于pid。

    (2)pid=-1:等待结合就是由父进程所有的子进程组成的。

  • 修改默认行为

    通过options设置:
    (1)WNOHANG:默认行为是挂起调用进程。

    (2)WUNTRACED:默认行为是只返回已终止的子进程。

    (3)WNOHANG|WUNTRACED:立即返回,如果等待集合中没有任何子进程被停止或者已终止,那么返回值为0,或者返回值等于那个被停止或者已经终止的子进程的PID。

  • 检查已回收子进程的退出状态

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

    (1)WIFEXITED:如果子进程通过调用exit或者一个返回正常终止,就返回真;

    (2)WEXITSTATUS:返回一个正常终止的子进程的退出状态。只有在WIFEXITED返回真时,才会定义这个状态。
    
  • 错误条件

    (1)若调用进程没有子进程,那么waitpid返回-1,并且设置errno为ECHILD;

    (2)若waitpid函数被一个信号中断,那么返回-1,并设置errno为EINTR

  • wait函数
    #include <sys/types.h>
    #include <sys/wait.h>

      	pid_t wait(int *status);
      	//返回:若成功,返回子进程的PID;若错误,返回-1。
    

    调用wait(&status)等价于调用waitpid(-1.&status,0)

8.4.4 让进程休眠
  • sleep函数:将进程挂起一段指定的时间
    #include <unistd.h>

      	unsigned int sleep(unsigned int secs);
      	//返回:还要休眠的秒数
    

    如果请求的时间量已经到了,返回0,否则返回还剩下的要休眠的秒数。

  • pause函数:让调用函数休眠,直到该进程收到一个信号。
    #include <unistd.h>

      	int pause(void);
      	//返回:总是-1
    
8.4.5 加载并运行程序
  • execve函数:在当前进程的上下文中加载并运行一个新程序。
    #include <unistd.h>

      	int execve(const char *filename,const char *argv[],const char *envp[]);
      	//返回:若成功,则不返回,若错误,返回-1
    

    filename:可执行目标文件

    argv:带参数列表

    envp:环境变量列表

    特点:execve调用一次从不返回

  • getenv函数:在环境数组中搜素字符串“name =VALUE”,若找到了,就返回一个指向value的指针,否则它就返回NULL。
    #include <stdlib.h>
    char *getenv(const char *name);
    //返回:存在,返回指向name的指针,若无匹配的,为NULL

    注意:
    execve函数在当前进程的上下文中加载并运行一个新的进程。它会覆盖当前进程的地址空间,并没有创建一个新的进程,新的进程仍然有相同的PID,并且继承了调用execve函数时已打开的所有文件描述符。

8.4.6 利用fork和execve运行程序
  • 外壳是一个交互型的应用级程序,它代表用户运行其他程序。

  • 外壳执行一系统的读/求值步骤,然后终止。读步骤读取来自用户的一个命令行,求值步骤解释命令行,并代表用户运行程序。

  • eval函数:对外壳命令行求值

  • parseline函数:解析外壳的一个输入

8.5 信号

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

  • P505:30种不同类型的信号表

8.5.1 信号术语
  • 发送信号:
    1)内核检测到一个系统事件

    2)一个进程调用了kill函数
    
  • 接收信号:当目的进程被内核强迫以某种方式对发送的信号作出反应时,目的进程就接收了信号

  • 待处理信号:一个只发出而没有被接收的信号

8.5.2 发送信号
  • 进程组

    每个进程都只属于一个进程组

    一个进程可以通过使用setpigd函数来改变自己或者其他进程的进程组

  • 用/bin/kill程序发送信号 •一个为负的PID会导致信号被发送到进程组PID中的每个进程中

  • 从键盘发送信号

  • 用kill函数发送信号

  • 用alarm函数发送信号

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)

代码托管连接

https://git.oschina.net/20145335/Linux-besti-is-2016-2017-1-20145335

心得体会

本周的知识点较上周多一些,代码的量的理解也大了许多,但是在周末之前就开始学习了,整体来说时间相对宽裕一些。在接受娄老师上周的点评后,本周对于代码不仅仅是编译运行一遍查看运行结果,更多的是对于函数的理解,深层次理解代码中函数的作用,收获相比起来也大了许多。

学习进度条

代码行数(新增/累积) 博客量(新增/累积) 学习时间(新增/累积) 重要成长
目标 5000行 30篇 400小时
第一周 200/200 2/2 20/20 掌握核心的linux命令,了解了linux操作系统
第二周 300/500 2/4 18/38 学会了虚拟机上的C编程
第三周 500/1000 3/7 22/60 初步学习计算机中各种数的表示和运算
第五周 300/1300 2/9 30/90 通过学习汇编,了解逆向的思想应用
第六周 300/1500 2/11 28/110 安装了Y86处理器,了解了ISA抽象
第七周 150/1700 2/13 30/130 学习了存储器的体系结构|
第八周 200/1900 2/15 30/150 复习前7章知识
第九周 340/2100 1/17 31/170 学习了系统级I/O
第十周 599/2700 1/19 33/200 学习了Linux命令
第十一周 1016/3300 1/21 35/240 学习了异常控制流

参考资料

原文地址:https://www.cnblogs.com/20145335hh/p/6107369.html