20145238 《信息安全系统设计基础》第11周学习总结
教材学习内容总结
一、课本知识梳理
1.控制流
- 从处理器加点开始,直到断点为止,PC假设一个值的序列 a0,a1,a2……,a(n-1)(其中,每个ak是某个相应的指令Ik的地址)。每次从ak到a(k+1)的过渡称为控制转移。这样的控制转移序列称为控制流。
2.异常控制流
- 概念:相较于最简单的“平滑序列”类型的控制流(即PC中相邻的指令在存储器中也相邻),程序变量表示的内部程序状态中的变化、系统状态的变化等突发情况使得控制系统做出的反映成为异常控制流(ECF)。
-意义:应用程序如何与操作系统实现交互。应用程序使用系统调用(system call)的ECF形式向操作系统请求服务;
实现并发的基本机制。
3.异常
- 产生:处理器中的变化(事件)触发从应用程序到异常处理程序的突发的控制转移,也就是异常;
- 类型:被零除,缺页,存储器访问违例,断点,算术溢出;系统调用,来着外部I/O设备的信号
- 处理:在任何情况下,当处理器检测到有事件发生时,它就会通过一张叫做异常表的跳转表进行一个间接过程调用,到一个专门处理这类时间的操作系统子程序(异常处理程序);
- 结果:当 exception handler处理结束之后,会有三种结果
处理程序将控制返回给事件发生的时候正在执行的指令;
处理程序将控制返回给如果没有发生异常将会执行的下一条指令;
处理程序终止被终端的程序
4.关于异常处理的返回问题
- 异常虽然类似于过程调用,但在压入栈的数据方面有不同。它会把一些额外的处理器状态压入栈中;并且如果是转移到内核的程序,压入的是内核栈中。 一旦触发异常,剩下的工作就由异常处理程序在软件中完成。在处理程序处理结束之后,它通过执行一条特殊的“从中断返回”指令,可选择地返回到被中断的程序。
5.异常种类 之 中断
- 原因:由I/O设备的信号引起的结果,属于异步(不是由任何一条指令造成的)。
过程: - i/o设备,例如定时器芯片、网络控制器等,通过处理器芯片上的一个引脚发信号,并将异常号(标识引起中断的设备)放在系统总线上;
在当前指令完成之后,处理器注意到引脚电压变化,从系统总线中读取异常信号,调用中断处理程序; - 处理中断:处理器返回(无中断的时候)应该执行的下一条指令。
6.异常种类 之 陷阱
- 原因:有意的异常,是执行指令的结果,属于同步。
- 解释:陷阱最重要的用途是在用户和程序与内核之间提供一个像过程一样的接口,即 系统调用。
- 过程:
用户程序需要或者希望向内核请求服务(比如创建或者终止进程、读文件等)的时候,执行 syscall n(n是想要请求的服务号)指令;
把控制权交给处理程序;
陷阱处理程序运行;
处理程序结束之后,返回到下一条指令。
区别:系统调用和函数调用对于程序使用者或者编写者来说并无差别,然而二者在实现的过程中有很大差别。系统调用运行在内核模式下,可以访问定义在其中的栈;而函数调用只能访问与被调用函数相同的栈。
7.异常类型 之 故障
- 原因:由潜在的可恢复的错误的情况引起,属于同步的;可能能够被修复然后返回当前指令。
- 过程
当前指令导致故障;
控制转移给处理程序;
故障处理程序运行,如果可以修正这个错误,就将控制引起故障的指令从而重新执行它;否则,返回内核中的abort例程,abort终止引起故障的程序。
8.异常类型 之 终止
- 原因:由不可恢复的致命错误造成;通常是一些硬件错误。
- 过程:
发生致命硬件错误;
传递控制给处理程序;
处理程序将控制返回给abort例程,该例程终止此应用程序
9.linuxIA32系统中的异常举例
- 除法错误:
当应用试图除以0的时候,或者当一个除法指令的结果对于目标操作数来说太大了,就会发生;
unix选择终止该程序(一般报告为浮点异常) - 一般故障保护:
异常号:13.通常是因为一个程序引用了一个未定义的虚拟存储区域,或者因为程序试图写一个只读的文本段;
linux不会试图恢复该类故障(一般报告为段故障) - 缺页
处理程序将磁盘上虚拟存储器相应的页面映射到物理存储器的一个页面,然后重新开始执行这条故障的指令 - 系统调用
linux提供上百种系统调用,每种都有对应的整数号(即内核跳转表中的偏移量);
在IA32系统上,系统调用通过一条int n指令提供;
在C程序中,通过syscall函数进行系统调用。
10.进程
定义:一个执行中的程序的实例。
- 系统中的每个程序都运行在某个进程的上下文中。上下文是由程序正确运行所需要的状态组成的。这个状态包括放在存储器中的程序的代码和数据等
- 每次用户通过向外壳输入一个可执行目标文件的名字,并运行一个程序的时候外壳就会创建一个新的进程;然后在这个新进程的上下文中运行这个可执行目标文件
- 应用程序也能够创建新的进程,然后再这个新进程的上下文中运行自己的代码或者其他应用程序
进程&程序 进程提供给了应用程序几个关键抽象:
- 一个独立的逻辑控制流——提供好像程序独占处理器的假象;
- 一个私有的地址空间——提供好像程序独占存储系统的假象;
11.并发流
- 引入:计算机系统中有很多逻辑流的不同形式,比如异常处理程序、进程、信号处理程序等;
- 概念:一个逻辑流的执行在时间上与另一个流重叠,称为并发流;多个流并发执行的现象称为并发;一个进程和其他进程轮流运行,称为多任务;又叫做时间分片。
对比:如果两个流并发地运行在不同的处理器核或者计算机上,那么我们称它们为并行流。
12.进程控制 之 获取进程ID
- 含义:每个进程都有一个唯一的进程ID(PID);
- 获取:getpid函数获取进程的PID;getppid获取创建调用进程的进程(即它的父进程)的PID。
- 注释:以上两个函数的返回值为pid_t,在linux系统中,它在types.h中被定义为int
13.进程控制 之 创建进程
- 过程:父进程通过调用fork函数来创建一个新的运行子进程
`
include <sys/types.h>
include <unistd.h>
pid_t fork(void);//子进程返回0;父进程返回子进程的PID;如果出错,则为-1`
特点:
- 新创建的子进程拥有和父进程相同的,但是独立的用户级虚拟地址空间拷贝,包括文本、数据和bss段、堆和用户栈等;【子进程相当于父进程的克隆体】
fork函数被创建之后,将返回两次:一次返回到父进程中,一次返回到子进程中;
子进程创建之后,与父进程并发执行;而二者执行的先后顺序是不可控制和预料的;
子进程和父进程拥有独立的地址空间,所以二者对某一相同变量值的修改是互相不受影响的。
共享文件
14.进程控制 之 终止进程
- 终止类型:
- 运行:进程要么在CPU上运行,要么在等待被执行且最终被内核调度;
- 停止:进程的执行被挂起,且不会被调度。【当进程收到SIGSTOP,SIGTSTP,SIDTTIN,SIGTTOU信号的时候,进程就会停止,并且保持停止直到它收到一个SIGCONT信号,在此时再次运行】
- 终止:进程永远地停止。三种原因:1)收到一个信号,其默认为终止程序;2)从主程序返回;3)调用exit函数(exit(int stauts),其中status是退出状态)
15.回收子进程
- 含义:当一个进程由于某种原因终止的时候,内核并不是把它从系统中清除,而是保持在已经终止的状态中,直到被它的父进程回收。这时,内核将子进程的退出状态传递给父进程,然后抛弃已经终止的进程。这之后,该进程才可以说是“不存在”了。
- 补充:已经终止但是尚未被回收的进程叫做僵死进程。
- 特例:如果父进程没有回收它的子进程就终止了,那么内核就会安排init函数来回收它们,init函数的返回值是1。(即使僵死子进程没有执行,也会消耗系统的存储器资源)
16.等待回收子进程
引入:一个进程可以通过调用waitpid函数来等待它的子进程终止或者停止
`#include<sys/types.h>
incldue<sys/wait.h>
pid_t waitpid(pid_t pid,int *status,int options);//如果成功,返回子进程的PID,如果为WNOHANG,则为0,其他错误则为-1
说明:默认地,即当options=0的时候,waitpid挂起调用进程的执行,直到它的等待集合中的一个子进程终止。如果等待集合中的一个进程在刚调用的时候就已经终止了,那么waitpid就立即返回。以上两种情况都会使得waitpid函数返回已经终止的子进程的PID,并且去除该进程。
`
解释
- 判断等待集合的成员
如果pid>0,那么等待的集合就是一个单独的子进程,它的进程ID等于pid; 如果pid<-1,那么等待集合就是由父进程的所有子进程组成的
修改默认行为
- 可以通过将options设置为常量WNOHANG和WUNTRACED的各种组合,修改默认行为: WNOHANG:如果等待集合中的任何子进程都还没有都还没有终止,那么就立即返回0; WUNTRACED:挂起调用进程的执行,直到等待集合中的一个变成已经终止的或者被停止,然后返回导致返回的子进程的PID; WNOHANG|WUNTRACED:立即返回。
检查已回收子进程的退出状态
-
非空的status参数会被放上status参数的关于返回的子进程的状态信息(wait.h定义了status参数的几个宏) WIFEXITED:如果子进程通过调用exit函数或者一个返回即return政策终止,就返回真; WEXITSTATUS:返回一个正常终止的子进程的退出状态(在WIFEXITED返回为真的时候才定义这个状态) WIFSINGALED:如果子进程是因为一个未被捕获的信号终止的,那么就返回真; WTERMSIG:返回导致子进程终止的信号的编号。只有在WIFSINGALED为真的时候,才定义这个状态。
-
错误条件 如果调用进程没有子进程,那么waitpid函数返回-1,并且设置errno为ECHLD;
如果waitpid函数被一个信号中断,那么它返回-1,并设置errno为EINTR
17.进程休眠
函数:
unsigned int sleep(unsigned int secs);//返回还要休眠的秒数
int pause(void);//该函数让调用函数休眠
18.加载并运行程序
函数:
include<unistd.h>
int execve(const char *filename,const char *argv[],const char *envp[]);//加载并运行可执行目标文件,如果成功则无返回值,如果不成功则返回-1
参数解释:
- argv变量指向一个以null结尾的指针数组,其中每个指针都指向一个参数串。按照惯例,argv[0]指向可执行目标文件的名字
- envp变量指向一个以null结尾的指针数组,其中每一个指针指向一个环境变量串,格式是“NAME = VALUE”
- 运行 在上面的函数加载了filename之后,它调用了启动代码。启动代码设置栈,将控制传给新程序的主函数
代码部分
- getenv函数
1.获得环境变量值的函数
2.参数是环境变量名name,例如”HOME”或者”PATH”。如果环境变量存在,那么getenv函数会返回环境变量值,即value的首地址;如果环境变量不存在,那么getenv函数返回NULL
- setenv函数
1.修改或添加环境变量的函数
2.将name设置成value
1.如果name在环境中不存在,那么很好办,在环境中添加这个新的变量就OK。
setenv函数必须在environment list中增加一个新的entry,然后动态申请存储空间来存储name=value,并且使entry指向该空间。
2.如果在环境中name已经存在,那么
(a)若overwrite非0,那么更新name的value(实质是更新环境表,指向新的value);
(b)若overwrite为0,则环境变量name不变,并且也不出错。
setenv函数不必在environment list中增加一个新的entry。当overwrite为0, 则不必改动entry的指向;当overwrite非0, 则直接使该entry指向name=value,当然该name=value也是存储在动态申请的内存里。
二、environvar.c
#include <stdio.h>
int main(void)
{
extern char **environ; //简单打印环境变量表
int i;
for(i = 0; environ[i] != NULL; i++)
printf("%s
", environ[i]);
return 0;
}
- 每个程序都有一个环境表,它是一个字符指针数组,其中每个指针包含一个以NULL结尾的C字符串的地址。全局变量environ则包含了该指针数组的地址。
三、FIFO.c
- FIFO是英文First In First Out 的缩写,是一种先进先出的数据缓存器。。FIFO存储器是一个先入先出的双口缓冲器,即第一个进入其内的数据第一个被移出,其中一个存储器的输入口,另一个口是存储器的输出口。
- FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。FIFO往往都是多个写进程,一个读进程。
consemer.c管道写端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FIFO_NAME "/tmp/myfifo"
#define BUFFER_SIZE PIPE_BUF
int main()
{
int pipe_fd;
int res;
int open_mode = O_RDONLY;
char buffer[BUFFER_SIZE + 1];
int bytes = 0;
memset(buffer, 0, sizeof(buffer));
printf("Process %d opeining FIFO O_RDONLY
", getpid());
pipe_fd = open(FIFO_NAME, open_mode);
printf("Process %d result %d
", getpid(), pipe_fd);
if (pipe_fd != -1) {
do {
res = read(pipe_fd, buffer, BUFFER_SIZE);
bytes += res;
} while (res > 0);
close(pipe_fd);
} else {
exit(EXIT_FAILURE);
}
printf("Process %d finished, %d bytes read
", getpid(), bytes);
exit(EXIT_SUCCESS);
}
producer.c管道读端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FIFO _NAME "/tmp/myfifo"
#define BUFFER_SIZE PIPE_BUF
#define TEN_MEG (1024 * 1024 * 10)
int main()
{
int pipe_fd;
int res;
int open_mode = O_WRONLY;
int bytes = 0;
char buffer[BUFFER_SIZE + 1];
if (access(FIFO_NAME, F_OK) == -1) {
res = mkfifo(FIFO_NAME, 0777);
if (res != 0) {
fprintf(stderr, "Could not create fifo %s
",
FIFO_NAME);
exit(EXIT_FAILURE);
}
}
printf("Process %d opening FIFO O_WRONLY
", getpid());
pipe_fd = open(FIFO_NAME, open_mode);
printf("Process %d result %d
", getpid(), pipe_fd);
if (pipe_fd != -1) {
while (bytes < TEN_MEG) {
res = write(pipe_fd, buffer, BUFFER_SIZE);
if (res == -1) {
fprintf(stderr, "Write error on pipe
");
exit(EXIT_FAILURE);
}
bytes += res;
}
close(pipe_fd);
} else {
exit(EXIT_FAILURE);
}
printf("Process %d finish
", getpid());
exit(EXIT_SUCCESS);
}
testmf.c
testmf.c展示了mkfifo函数的用法
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
int res = mkfifo("/tmp/myfifo", 0777);//定义函数
if (res == 0) { //若成功返回0,否则返回-1
printf("FIFO created
");
}
exit(EXIT_SUCCESS);
}
- 1.会依参数pathname建立特殊的FIFO文件,该文件必须不存在,而参数mode为该文件的权限(mode%~umask),因此 umask值也会影响到FIFO文件的权限。Mkfifo()建立的FIFO文件其他进程都可以用读写一般文件的方式存取。当使用open()来打开 FIFO文件时,O_NONBLOCK旗标会有影响
- a.当使用O_NONBLOCK 旗标时,打开FIFO 文件来读取的操作会立刻返回,但是若还没有其他进程打开FIFO 文件来读取,则写入的操作会返回ENXIO 错误代码。
- b.没有使用O_NONBLOCK 旗标时,打开FIFO 来读取的操作会等到其他进程打开FIFO文件来写入才正常返回。同样地,打开FIFO文件来写入的操作会等到其他进程打开FIFO 文件来读取后才正常返回。
listargs.c
#include <stdio.h>
main( int ac, char *av[] )
{
int i;
printf("Number of args: %d, Args are:
", ac);
for(i=0;i<ac;i++)
printf("args[%d] %s
", i, av[i]);
fprintf(stderr,"This message is sent to stderr.
");
}
- 说明shell并不将重定向标记和文件名传递给程序
(1).shell并不将重定向标记和文件名传递给程序。
(2).重定向可以出现在命令行中的任何地方,且不需要空格来区分。
(3).shell提供对重定向向其他文件描述符的支持。例如,2>filename即将重定向描述符2,也就是将标准错误输出到给定的文件中。
(4).是shell,而非程序将输入和输出重定向的。
pipedemoc
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include <unistd.h>
int main()
{
int len, i, apipe[2]; //apipe数组中存储两个文件的描述符
char buf[BUFSIZ];
f ( pipe ( apipe ) == -1 ){
perror("could not make pipe");
exit(1);
}
printf("Got a pipe! It is file descriptors: { %d %d }
",
apipe[0], apipe[1]);
while ( fgets(buf, BUFSIZ, stdin) ){ //从标准输入读入数据,放到缓冲区
len = strlen( buf );
if ( write( apipe[1], buf, len) != len ){
//向apipe[1](即管道写端)写入数据
perror("writing to pipe");
break;
}
for ( i = 0 ; i<len ; i++ ) //清理缓冲区
buf[i] = 'X' ;
len = read( apipe[0], buf, BUFSIZ ) ; //从apipe[0](即管道读端)读数据
if ( len == -1 ){
perror("reading from pipe");
break;
}
if ( write( 1 , buf, len ) != len ){ //把从管道读出的数据再写到标准输出
perror("writing to stdout");
break;
}
}
}
-
who把输出送给stdout,sort从stdin中读入数据,那也就是说who的stdout和sort的stdin连成了一个。
-
result=pipe(int array[2]);array[0]是读端的文件描述符,array[1]是写端的文件描述符。
-
pipe调用首先获得两个“最低可用文件描述符”,赋给array[0]和array[1],然后再把这两个文件描述符连接起来。
pipedemo2.c
-
在程序中。显示来从键盘到进程,从进程到管道,再从管道到进程以及从进程回到终端的数据传输流
#include <stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #define CHILD_MESS "I want a cookie " #define PAR_MESS "testing.. " #define oops(m,x) { perror(m); exit(x); } //还可以这样宏定义语句块 main() { int pipefd[2]; int len; char buf[BUFSIZ]; int read_len; if ( pipe( pipefd ) == -1 ) // 创建一个管道:apipe[0]读,apipe[1]写 oops("cannot get a pipe", 1); switch( fork() ){ case -1: oops("cannot fork", 2); case 0: len = strlen(CHILD_MESS); while ( 1 ){ if (write( pipefd[1], CHILD_MESS, len) != len ) oops("write", 3); sleep(5); } default: len = strlen( PAR_MESS ); while ( 1 ){ if ( write( pipefd[1], PAR_MESS, len)!=len ) oops("write", 4); sleep(1); read_len = read( pipefd[0], buf, BUFSIZ ); if ( read_len <= 0 ) break; write( 1 , buf, read_len ); } } }
whotofile.c
- 代码利用它们是的Unix下的程序可以轻易地将标准输入、输出和错误信息输出连接到文件:
a、标准输入、输出以及错误输出分别对应于文件描述符0、1、2;
b、内核总是使用最低可用文件描述符;
c、文件描述符集合通过exec调用传递,且不会被改变。
#include <stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
int pid ;
int fd;
printf("About to run who into a file
");
/* create a new process or quit */
if( (pid = fork() ) == -1 ){
perror("fork"); exit(1);
}
/* child does the work */
if ( pid == 0 ){
close(1); /* close, */
fd = creat( "userlist", 0644 ); /* then open */
execlp( "who", "who", NULL ); /* and run */
perror("execlp");
exit(1);
}
/* parent waits then reports */
if ( pid != 0 ){
wait(NULL);
printf("Done running who. results in userlist
");
}
return 0;
}
sigactdemo.c
#include <stdio.h>
#include<unistd.h>
#include <signal.h>
#define INPUTLEN 100
void inthandler();
int main()
{
struct sigaction newhandler;
sigset_t blocked; //被阻塞的信号集
char x[INPUTLEN];
newhandler.sa_handler = inthandler;
newhandler.sa_flags = SA_RESTART|SA_NODEFER
|SA_RESETHAND;
sigemptyset(&blocked); //清空信号处理掩码
sigaddset(&blocked, SIGQUIT);
newhandler.sa_mask = blocked;
if (sigaction(SIGINT, &newhandler, NULL) == -1)
perror("sigaction");
else
while (1) {
fgets(x, INPUTLEN, stdin); //fgets()会在数据的最后附加" "
printf("input: %s", x);
}
return 0;
}
void inthandler(int s)
{
printf("Called with signal %d
", s);
sleep(s * 4);
printf("done handling signal %d
", s);
}
sigactdemo2.c
-
休息seconds秒后返回;或者被信号中断且信号处理函数返回后sleep()返回0。所以如果不计较返回值的话,pause()的功能相当于无限期的sleep()。
#include <unistd.h> #include <signal.h> #include <stdio.h> void sig_alrm( int signo ) { /*do nothing*/ } unsigned int mysleep(unsigned int nsecs) { struct sigaction newact, oldact; unsigned int unslept; newact.sa_handler = sig_alrm; sigemptyset( &newact.sa_mask ); newact.sa_flags = 0; sigaction( SIGALRM, &newact, &oldact ); alarm( nsecs ); pause(); unslept = alarm ( 0 ); sigaction( SIGALRM, &oldact, NULL ); return unslept; } int main( void ) { while( 1 ) { mysleep( 2 ); printf( "Two seconds passed " ); } return 0; }
exec1.c
#include <stdio.h>
#include <unistd.h>
int main()
{
char *arglist[3];
arglist[0] = "ls";
arglist[1] = "-l";
arglist[2] = 0 ;//NULL
printf("* * * About to exec ls -l
");
execvp( "ls" , arglist );
printf("* * * ls is done. bye");
return 0;
}
- 第二个储存程序参数的argv数组应注意两点,1、数组的第一个元素应置为程序名称2、数组应以NULL结尾
forkdemo4.c
#include <stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
int fork_rv;
printf("Before: my pid is %d
", getpid());
fork_rv = fork(); /* create new process */
if ( fork_rv == -1 ) /* check for error */
perror("fork");
else if ( fork_rv == 0 ){
printf("I am the child. my pid=%d
", getpid());
printf("parent pid= %d, my pid=%d
", getppid(), getpid());
exit(0);
}
else{
printf("I am the parent. my child is %d
", fork_rv);
sleep(10);
exit(0);
}
return 0;
}
psh1.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include<unistd.h>
#define MAXARGS 20
#define ARGLEN 100
int execute( char *arglist[] )
{
execvp(arglist[0], arglist);
perror("execvp failed");
exit(1);
}
char * makestring( char *buf )
{
char *cp;
buf[strlen(buf)-1] = ' ';
cp = malloc( strlen(buf)+1 );
if ( cp == NULL ){
fprintf(stderr,"no memory
");
exit(1);
}
strcpy(cp, buf);
return cp;
}
int main()
{
char *arglist[MAXARGS+1];
int numargs;
char argbuf[ARGLEN];
numargs = 0;
while ( numargs < MAXARGS )
{
printf("Arg[%d]? ", numargs);
if ( fgets(argbuf, ARGLEN, stdin) && *argbuf != '
' )
arglist[numargs++] = makestring(argbuf);
else
{
if ( numargs 0 ){
arglist[numargs]=NULL;
execute( arglist );
numargs = 0;
}
}
}
return 0;
}
waitdemo2.c 获取子进程状态
#include <stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#define DELAY 10
void child_code(int delay)
{
printf("child %d here. will sleep for %d seconds
", getpid(), delay);
sleep(delay);
printf("child done. about to exit
");
exit(27);
}
void parent_code(int childpid)
{
int wait_rv;
int child_status;
int high_8, low_7, bit_7;
wait_rv = wait(&child_status);
printf("done waiting for %d. Wait returned: %d
", childpid, wait_rv);
high_8 = child_status >> 8; /* 1111 1111 0000 0000 */
low_7 = child_status & 0x7F; /* 0000 0000 0111 1111 */
bit_7 = child_status & 0x80; /* 0000 0000 1000 0000 */
printf("status: exit=%d, sig=%d, core=%d
", high_8, low_7, bit_7);
}
int main()
{
int newpid;
printf("before: mypid is %d
", getpid());
if ( (newpid = fork()) == -1 )
perror("fork");
else if ( newpid == 0 )
child_code(DELAY);
else
parent_code(newpid);
}
- wait阻塞调用它的程序直到子进程结束,返回结束进程的PID,父进程通过传给wait的参数中获取子进程以何种方式退出。如果子进程调用exit退出,那么内核把exit的返回值存放到这个整数变量中的高八位,如果进程是被杀死的,那么内核将信号序号存放在这个变量的低7位,中间一位用来指明发生错误并产生了core dump。
教材学习中的问题和解决过程
1.对管道的理解还不是特别明确,又查了一些资料对管道做了进一步的解释:
- 管道是一种两个进程间进行单向通信的机制。因为管道传递数据的单向性,管道又称为半双工管道。管道的这一特点决定了器使用的局限性。管道是Linux支持的最初Unix IPC形式之一,具有以下特点:
数据只能由一个进程流向另一个进程(其中一个读管道,一个写管道);如果要进行双工通信,需要建 立两个管道。
管道只能用于父子进程或者兄弟进程间通信。,也就是说管道只能用于具有亲缘关系的进程间通信。
- 通过管道通信的两个进程,一个进程向管道写数据,另外一个从中读数据。写入的数据每次都添加到管道缓冲区的末尾,读数据的时候都是从缓冲区的头部读出数据的。
本周代码托管截图
其他(感悟、思考等,可选)
本周内容和上周一样都是理解代码,通过代码来实现命令能够更好的掌握其中的原理,可以使自己模拟计算机系统结构,对数据进行处理。但是感觉代码理解还是有一定的难度,必须要在十分了解函数作用的前提下才能较为抽象的理解代码,对于C语言比较薄弱的同学比如我还是非常吃力的。
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 200/200 | 2/2 | 20/20 | |
第二周 | 300/500 | 2/4 | 18/38 | |
第三周 | 500/1000 | 3/7 | 22/60 | |
第四周 | 300/1300 | 2/9 | 30/90 | |
第五周 | 500/1000 | 3/12 | 22/120 | |
第六周 | 100/1300 | 2/15 | 30/150 | |
第七周 | 500/1000 | 3/18 | 22/180 | |
第八周 | 100/1500 | 2/20 | 30/210 | |
第九周 | 500/1600 | 2/22 | 32/242 | |
第十周 | 500/2100 | 4/26 | 40/274 |
参考资料
- 《深入理解计算机系统V2》学习指导
- linux i/o重定向与管道编程
- 《linux有名管道》