20145206 《信息安全系统设计基础》第十一周学习总结
一、教材学习内容总结
第八章 异常控制流
·平滑:指在存储器中指令都是相邻的。
·突变:出现不相邻,通常由诸如跳转、调用、和返回等指令造成。
·异常控制流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、逻辑控制流
一系列的程序计数器(PC)的值,这些值唯一地对应于包含在程序的可执行目标文件中的指令,或者是包含在运行时动态链接到程序的共享对象中的指令。这个PC值的序列叫做逻辑控制流,或者简称逻辑流。
关键点在于进程是轮流使用处理器的。每个进程执行它的流的一部分,然后被抢占,然后轮到其他进程,就像是在独占地使用处理器。
2、并发流
一个逻辑流的执行在时间上与另一个流重叠,称为并发流,这两个流被称为并发并发地运行。
多个流并发地执行的一般现象称为并发。一个进程和其他进程轮流运行的概念称为多任务。一个进程执行它的控制流的一部分的每一个时间段叫做时间片。
如果两个流并发地运行在不同的处理器核或者计算机上,那么我们称它们为并发流,它们并行地运行,且并行地执行。
3、私有地址空间
进程也为每个程序提供一种假象,好像它独占地使用系统空间。一般而言,和这个空间中某个地址相关联的那个存储器字节是不能被其他进城读或者写的。
4、用户模式和内核模式
处理器通常是用某个控制寄存器中的一个模式位来提供这种功能的,该寄存器描述了进程当前享有的特权。当设置了模式位时,进程就运行在内核模式中。
进程从用户模式变成内核模式的唯一方法是通过诸如中断、故障或者陷入系统调用这样的异常。
Linux提供了一种聪明的机制,叫做/proc文件系统,它允许用户模式进程访问内核数据结构的内容。
5、上下文切换
操作系统内核使用一种称为上下文切换的较高形式的异常控制流来实现多任务。上下文切换机制是建立在较低层异常机制之上的。
上下文就是内核重新启动一个被抢占的进程所需的状态。由一些对象的值组成,这些对象包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构,如页表、进程表、文件表。
在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程。这种决定就叫做调度,是由内核中称为调度器的代码处理的。
上下文切换:
(1)保存当前进程的上下文
(2)恢复某个先前被抢占的进程被保存的上下文
(3)将控制传递给这个新恢复的进程。
当内核代表用户执行系统调用时,可能会发生上下文切换,中断也可能引发上下文切换。
第三节 系统调用错误处理
这一节主要是附录A的内容的重复解释,在上周已经学习过。
简单总结就是,系统会使用错误处理包装函数,系统级函数是小写,他们的包装函数名大写,包装函数调用基本函数,有任何问题就终止,如果没有问题和基本函数是一样的。
需要注意的就是,检查错误的思想!!!
第四节、进程控制
1、获取进程ID
每个进程都有一个唯一的正数(非零)进程ID(PID)。
#include<sys/types.h>
#include<unistd.h>
pid_t getpid(void);
pid_t getppid(void);
返回:调用者或其父进程的PID
2、创建和终止进程
运行
停止
终止:1)收到一个信号,该信号的默认行为是终止进程;2)从主程序返回;3)调用exit函数。
#include<stdlib.h>
Void exit(int status);
该函数无返回值。
父进程通过调用fork函数创建一个新的运行子进程。只被调用一次,却会返回两次。在父进程中,fork返回子进程的PID。在子进程中,fork返回0。
调用一次,返回两次。
并发执行。内核能够以任意方式交替执行它们的逻辑控制流中的指令。
相同的但是独立的地址空间。
共享文件。子进程继承了父进程所有的打开文件。
3、回收子进程
一个终止了但还未被回收的进程称为僵死进程。
如果父进程没有回收它的僵死进程就终止了,那么内核就会安排init进程来回收它们。Init进程的PID为1。
一个进程可以通过调用waitpid函数来等待它的子进程终止或者停止。
#include<sys/types.h>
#include<sys/wait.h>
pid_t waitpid(pid_t pid,int *status,int options);
1.判断等待集合的成员
如果pid>0,那么等待的集合就是一个单独的子进程,它的进程ID等于pid。
如果pid=-1,那么等待集合就是由父进程所有的子进程组成的。
2.修改默认行为
通过将optioins设置为常量WNOHANG和WUNTRACED的各种组合。
WNOHANG:如果等待集合中的任何子进程都还没有终止,那么就立即返回(返回值为0)。
WUNTRACED:挂起调用进程的执行,直到等待集合中的一个进程变成已终止或者被停止的。
WNOHANG|WUNTRACED:立即返回,如果等待集合中没有任何子进程被停止或已终止,那么返回值为0,或者返回值等于那个被停止或者已终止的子进程的PID。
3.检查已回收子进程的退出状态
wait.h头文件定义了解释status参数的几个宏:
WIFEXITED:如果子进程通过调用exit或者一个返回(return)正常终止,就返回真。
WEXITSTATUS:返回一个正常终止的子进程的退出状态。只有在WIFEXITED返回为真时,才会定义这个状态。
WIFSIGNALED:如果子进程是因为一个未被捕获的信号终止的,那么就返回真。
WTERMSIG:返回导致子进程终止的信号的编号。只有在WIFSIGNALED返回为真时,才定义这个状态。
WIFSTOPPED:如果引起返回的子进程当前是被停止的,那么就返回真。
WSTOPSIG:返回引起子进程停止的信号的数量。只有在WIFSTOPPED返回为真时,才定义这个状态。
4.错误条件
如果调用进程没有子进程,那么waitpid返回-1,并且设置errno为ECHILD。如果waitpid函数被一个信号中断,那么它返回-1,并设置errno为EINTR
5.wait函数
wait函数是waitpid函数的简单版本:
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int *status);
调用wait(&status)等价于调用waitpid(-1,&status,0)。
4、让进程休眠
sleep函数将一个进程挂起一段指定的时间。
#include<unistd.h>
unsigned int sleep(unsigned int secs);
pause函数,让调用函数休眠,直到该进程收到一个信号。
#include<unisted.h>
int pause(void);
5、加载并运行程序
execve函数在当前进程的上下文中加载并运行一个新程序。
#include<unistd.h>
int execve(const char *filename,const char *argv[],
const char *envp[])
可执行文件filename,带参数列表argv和环境变量列表envp。execve调用一次从不返回。
#include<stdlib.h>
char *getenv(const char *name);
返回:若存在则为指向name的指针,若无匹配的,则为NULL。
getenv函数在环境数组中搜索字符串“name=value”。如果找到了,它就返回一个指向value的指针,否则它就返回NULL。
#include<stdlib.h>
int setenv(const char *name,const char *newvalue,int overwrite);
void unsetenv(const char *name);
如果环境数组包含一个形如“name=oldvalue”的字符串,那么unsetenv会删除它,而setenv会用newvalue代替oldvalue,但是只有在overwrite非零时才会这样。如果name不存在,那么setenv就把“name=value”添加到数组中。
第五节 信号
Unix信号:更高层的软件形式的异常允许进程中断其他进程。
一、信号术语
传递一个信号到目的进程的两个步骤:发送信号和接收信号。
发送信号的原因:
1.内核检测到一个系统事件
2.一个进程调用了kill函数,显式的要求内核发送一个信号给目的进程。
一个进程可以发送信号给它自己。
接收信号:
1.忽略
2.终止
3.执行信号处理程序,捕获信号
待处理信号:
只发出没有被接收的信号
任何时刻,一种类型至多只会有一个待处理信号,多的会被直接丢弃
一个进程可以选择性的阻塞接受某种信号,被阻塞仍可以被发送,但是不会被接收
一个待处理信号最多只能被接收一次。
pending:待处理信号集合
blocked:被阻塞信号集合。
二、发送信号——基于进程组
1.进程组
每个进程都只属于一个进程组。
进程组ID:正整数
一个子进程和他的父进程属于同一进程组。
查看进程组id:getpgrp
修改进程组:setpgid
2.用/bin/kill程序发送信号
/bin/kill程序可以向另外的进程发送任意的信号,格式是:
/bin/kill -n m
n是信号,m是进程或进程组
当n>0时,发送信号n到进程m
当n<0时,使信号|n|发送到进程组m中的所有进程。
3.从键盘发送信号
4.用kill函数发送信号
进程通过调用kill函数发送信号给其他进程。
具体同本节2.
5.用alarm函数发送信号
进程可以通过调用alarm函数向它自己发送SIGALRM信号。
#include <unistd.h>
unsigned int alarm(unsigned int secs);
返回前一次闹钟剩余的秒数,若没有返回0.
第六节 非本地跳转
c语言中,用户级的异常控制流形式,通过setjmp和longjmp函数提供。
setjump函数在env缓冲区中保存当前调用环境,以供后面longjmp使用,并返回0.
调用环境:程序计数器,栈指针,通用目的寄存器
longjmp函数从env缓冲区中恢复调用环境,然后触发一个从最近一次初始化env的setjmp调用的返回。然后setjmp返回,并带有非零的返回值retval。
注:
setjmp函数只被调用一次,但返回多次;
longjmp函数被调用一次,但从不返回。
第七节 操作进程的工具
STRACE:打印一个正在运行的程序和他的子程序调用的每个系统调用的痕迹
PS:列出当前系统中的进程,包括僵死进程
TOP:打印出关于当前进程资源使用的信息
PMAP:显示进程的存储器映射
二、本周代码学习
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\n");
execvp( "ls" , arglist );//第一个参数传递的是文件名
printf("* * * ls is done. bye");
return 0;
}
功能:装入并运行其它程序的函数
分析:
根据代码我们知道,执行的指令是:
ls -l
开始显示“ * * * About to exec ls -l”
执行完之后应该显示“ * * * ls is done. bye”
然后从上图中,我们可以看到,并没有显示最后一句,这是因为在系统处理器中,在执行execvp( "ls" , arglist );语句时,已经将最后的打印语句覆盖掉了,处理器中并没有这句打印语句,所以最终结果如图所示。
exec2.c
#include <stdio.h>
#include <unistd.h>
int main()
{
char *arglist[3];
arglist[0] = "ls";
arglist[1] = "-l";
arglist[2] = 0 ;
printf("* * * About to exec ls -l\n");
execvp( arglist[0] , arglist );
printf("* * * ls is done. bye\n");
}
功能:装入并运行其它程序的函数
分析:
exec2.c的代码运行结果与exec1.c的结果相同,说明两者实现的功能是一样的,但是唯一的区别就在于
execvp( arglist[0] , arglist );
//将文件名存放在arglist[0]中
这个语句上,在exec1.c中,这句代码这这样写的:
execvp( "ls" , arglist );
也就是把“ls”替换成了“arglist[0]”,所以并不会影响结果。
exec3.c
#include <stdio.h>
#include <unistd.h>
int main()
{
char *arglist[3];
char *myenv[3];
myenv[0] = "PATH=:/bin:";
myenv[1] = NULL;
arglist[0] = "ls";
arglist[1] = "-l";
arglist[2] = 0 ;
printf("* * * About to exec ls -l\n");
// execv( "/bin/ls" , arglist );
// execvp( "ls" , arglist );
// execvpe("ls" , arglist, myenv);
execlp("ls", "ls", "-l", NULL);
printf("* * * ls is done. bye\n");
}
功能:装入并运行其它程序的函数
forkdemo1.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int ret_from_fork, mypid;
mypid = getpid();
printf("Before: my pid is %d\n", mypid);
ret_from_fork = fork();
sleep(1);
printf("After: my pid is %d, fork() said %d\n",
getpid(), ret_from_fork);
return 0;
}
fork函数:
将运行着的程序分成2个(几乎)完全一样的进程,每个进程都启动一个从代码的同一位置开始执行的线程。若成功调用一次则返回两个值,子进程返回0,父进程返回子进程ID;否则,出错返回-1。
分析:
从图中可以看出,After打印语句打印了两次,第一次打印的After语句是父进程执行的,因为fork函数的返回值不是0,说明是父进程在执行,第二次打印的After语句是子进程执行的,因为fork函数的返回值是0,而我的id是子进程id。
forkdemo2.c
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("before:my pid is %d\n", getpid() );
fork();
fork();
printf("aftre:my pid is %d\n", getpid() );
return 0;
}
分析:
因为执行了两次fork函数,执行第一次,分出2个线程,执行第二次,之前的两个线程分别分出2个线程,所以一共是四个线程,最终出现4次After语句。
forkdemo3.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int fork_rv;
int main()
{
printf("Before: my pid is %d\n", 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 parent. my child is %d\n", getpid());
exit(0);
}
else{
printf("I am the parent. my child is %d\n", fork_rv);
exit(0);
}
return 0;
}
分析:
fork函数会将一个进程分成两个进程,并且会返回两次,所以如上图所示,我们可以看到,出现了一次“I am the parent. my child is 17523”,又出现了一次“I am the parent. my child is 17523”。这个c文件,还包括了错误处理,提高了代码的健壮性。
forkdemo4.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
int fork_rv;
printf("Before: my pid is %d\n", 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\n", getpid());
printf("parent pid= %d, my pid=%d\n", getppid(), getpid());
exit(0);
}
else{
printf("I am the parent. my child is %d\n", fork_rv);
sleep(10);
exit(0);
}
return 0;
}
现象:
最后一行是10s之后才出现的
分析:
这是因为sleep(10)函数,使父进程睡眠10s再执行exit(0)语句。
forkgdb.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int gi=0;
int main()
{
int li=0;
static int si=0;
int i=0;
pid_t pid = fork();
if(pid == -1){
exit(-1);
}
else if(pid == 0){
for(i=0; i<5; i++){
printf("child li:%d\n", li++);
sleep(1);
printf("child gi:%d\n", gi++);
printf("child si:%d\n", si++);
}
exit(0);
}
else{
for(i=0; i<5; i++){
printf("parent li:%d\n", li++);
printf("parent gi:%d\n", gi++);
sleep(1);
printf("parent si:%d\n", si++);
}
exit(0);
}
return 0;
}
分析:
开始时,先进入父进程的循环,打印parent li:0和parent gi:0,接着休眠一秒,子进程打印child li:0,休眠一秒,父进程接着打印parent si: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] = '\0';
cp = malloc( strlen(buf)+1 );
if ( cp == NULL ){
fprintf(stderr,"no memory\n");
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 != '\n' )
arglist[numargs++] = makestring(argbuf);
else
{
if ( numargs > 0 ){
arglist[numargs]=NULL;
execute( arglist );
numargs = 0;
}
}
}
return 0;
}
分析:
首先while循环输入命令,并将输入的值转换为字符串型,直到输入回车换行时,调用execute函数,将存储命令的数组作为参数传入,实现执行指令的功能。
psh2.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
#define MAXARGS 20
#define ARGLEN 100
char *makestring( char *buf )
{
char *cp;
buf[strlen(buf)-1] = '\0';
cp = malloc( strlen(buf)+1 );
if ( cp == NULL ){
fprintf(stderr,"no memory\n");
exit(1);
}
strcpy(cp, buf);
return cp;
}
void execute( char *arglist[] )
{
int pid,exitstatus;
pid = fork();
switch( pid ){
case -1:
perror("fork failed");
exit(1);
case 0:
execvp(arglist[0], arglist);
perror("execvp failed");
exit(1);
default:
while( wait(&exitstatus) != pid )
;
printf("child exited with status %d,%d\n",
exitstatus>>8, exitstatus&0377);
}
}
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 != '\n' )
arglist[numargs++] = makestring(argbuf);
else
{
if ( numargs > 0 ){
arglist[numargs]=NULL;
execute( arglist );
numargs = 0;
}
}
}
return 0;
}
功能:
在子进程中执行用户输入的指令,利用wait函数,通过父进程,实现循环输入指令
分析:
这个代码与psh1.c代码最大的区别就在于execute函数。
调用wait(&status)等价于调用waitpid(-1.&status,0),当option=0时,waitpid挂起调用进程的执行,直到它的等待集合中的一个子进程终止。只要有一个子进程没有结束,父进程就被挂起。所以当wait返回pid时没说明,子进程都已经结束,即用户输入的指令都已经执行完毕。因为execute函数在大的循环中调用,所以会循环执行下去,除非用户强制退出。
另外,当子进程正常执行完用户指令后,子进程的状态为0,若执行指令出错,子进程的状态为1.
testbuf1.c
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("hello");
fflush(stdout);
while(1);
}
功能:
打印hello,但没有结束进程,若此时向标准输入设备中输入数据,屏幕上会显示
分析:
打印hello,但没有结束进程,若此时向标准输入设备中输入数据,屏幕上会显示出来。必须用户强制退出,才能退出程序。
testbuf2.c
#include <stdio.h>
int main()
{
printf("hello\n");
while(1);
}
功能:
同testbuf1.c一样
分析:
从testbuf1.c和testbuf2.c的运行结果上看,我们可以猜出fflush(stdout);的功能就是打印换行符。
testbuf3.c
int main()
{
fprintf(stdout, "1234", 5);
fprintf(stderr, "abcd", 4);
}
testpid.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
printf("my pid: %d \n", getpid());
printf("my parent's pid: %d \n", getppid());
return 0;
}
功能:
显示进程的id
分析:
显示进程id和其父进程的id
testpp.c
#include <stdio.h>
#include <stdlib.h>
int main()
{
char **pp;
pp[0] = malloc(20);
return 0;
}
问题在于没给pp分配空间就调用了pp[0],毕竟声明的时候只是一个指针,而指针必须要初始化。
应该改成:
include <stdio.h>
include <stdlib.h>
int main()
{
char pp;
pp = (char)malloc(20);
pp[0] = (char*)malloc(20);
return 0;
}
testsystem.c
#include <stdlib.h>
int main ( int argc, char *argv[] )
{
system(argv[1]);
system(argv[2]);
return EXIT_SUCCESS;
} /* ---------- end of function main ---------- */
分析:
system函数:发出一个DOS命令
用 法: int system(char *command);
system函数需加头文件<stdlib.h>后方可调用。
最终就是执行用户输入的指令。
waitdemo1.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define DELAY 4
void child_code(int delay)
{
printf("child %d here. will sleep for %d seconds\n", getpid(), delay);
sleep(delay);
printf("child done. about to exit\n");
exit(17);
}
void parent_code(int childpid)
{
int wait_rv=0; /* return value from wait() */
wait_rv = wait(NULL);
printf("done waiting for %d. Wait returned: %d\n",
childpid, wait_rv);
}
int main()
{
int newpid;
printf("before: mypid is %d\n", getpid());
if ( (newpid = fork()) == -1 )
perror("fork");
else if ( newpid == 0 )
child_code(DELAY);
else
parent_code(newpid);
return 0;
}
功能:
验证父子进程的调用顺序,测试函数sleep、wait在进程调用中的作用。
分析:
我们可以看到hildcode函数里,调用了sleep函数,这表示执行完printf("child %d here. will sleep for %d seconds\n", getpid(), delay);语句后,系统休眠4s继续进行。
为什么这里不会让父进程继续进行?本来父子进程时并发执行的,按理说应该子进程休眠,父进程正常执行的,是因为parentcode函数里的wait_rv = wait(NULL);代码,说明要子程序执行完毕,父进程才能继续往下进行。
所以最终结果如上图所示。
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\n", getpid(), delay);
sleep(delay);
printf("child done. about to exit\n");
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\n", 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\n", high_8, low_7, bit_7);
}
int main()
{
int newpid;
printf("before: mypid is %d\n", getpid());
if ( (newpid = fork()) == -1 )
perror("fork");
else if ( newpid == 0 )
child_code(DELAY);
else
parent_code(newpid);
}
功能:
在waitdemo1.c的基础上,设置了状态位。
分析:
waitdemo2.c和waitdemo1.c最大的不同就是:设置了子进程结束后父进程的状态位。
argtest.c
#include <stdio.h>
#include <stdlib.h>
#include "argv.h"
int main(int argc, char *argv[]) {
char delim[] = " \t";
int i;
char **myargv;
int numtokens;
if (argc != 2) {
fprintf(stderr, "Usage: %s string\n", argv[0]);
return 1;
}
if ((numtokens = makeargv(argv[1], delim, &myargv)) == -1) {
fprintf(stderr, "Failed to construct an argument array for %s\n", argv[1]);
return 1;
}
printf("The argument array contains:\n");
for (i = 0; i < numtokens; i++)
printf("%d:%s\n", i, myargv[i]);
execvp(myargv[0], myargv);
return 0;
}
freemakeargv.c
#include <stdlib.h>
#include "argv.h"
void freemakeargv(char **argv) {
if (argv == NULL)
return;
if (*argv != NULL)
free(*argv);
free(argv);
}
makeargv.c
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include "argv.h"
int makeargv(const char *s, const char *delimiters, char ***argvp) {
int error;
int i;
int numtokens;
const char *snew;
char *t;
if ((s == NULL) || (delimiters == NULL) || (argvp == NULL)) {
errno = EINVAL;
return -1;
}
*argvp = NULL;
snew = s + strspn(s, delimiters);
if ((t = malloc(strlen(snew) + 1)) == NULL)
return -1;
strcpy(t, snew);
numtokens = 0;
if (strtok(t, delimiters) != NULL)
for (numtokens = 1; strtok(NULL, delimiters) != NULL; numtokens++) ;
if ((*argvp = malloc((numtokens + 1)*sizeof(char *))) == NULL) {
error = errno;
free(t);
errno = error;
return -1;
}
if (numtokens == 0)
free(t);
else {
strcpy(t, snew);
**argvp = strtok(t, delimiters);
for (i = 1; i < numtokens; i++)
*((*argvp) + i) = strtok(NULL, delimiters);
}
*((*argvp) + numtokens) = NULL;
return numtokens;
}
environ.c
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
printf("PATH=%s\n", getenv("PATH"));
setenv("PATH", "hello", 1);
printf("PATH=%s\n", getenv("PATH"));
#if 0
printf("PATH=%s\n", getenv("PATH"));
setenv("PATH", "hellohello", 0);
printf("PATH=%s\n", getenv("PATH"));
printf("MY_VER=%s\n", getenv("MY_VER"));
setenv("MY_VER", "1.1", 0);
printf("MY_VER=%s\n", getenv("MY_VER"));
#endif
return 0;
}
功能:
打印设置环境变量的值
分析:
如图所示:先打印了一开始的初始环境变量,接着重新设置环境变量,并打印输出。
environvar.c
#include <stdio.h>
int main(void)
{
extern char **environ;
int i;
for(i = 0; environ[i] != NULL; i++)
printf("%s\n", environ[i]);
return 0;
}
分析:
将外部变量environ的内容打印出来,也就是把系统相关宏值,打印出来。
consumer.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 \n", getpid());
pipe_fd = open(FIFO_NAME, open_mode);//判断打开文件是否成功
printf("Process %d result %d\n", 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\n", 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 \n",
FIFO_NAME);
exit(EXIT_FAILURE);
}
}
printf("Process %d opening FIFO O_WRONLY\n", getpid());
pipe_fd = open(FIFO_NAME, open_mode);
printf("Process %d result %d\n", 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\n");
exit(EXIT_FAILURE);
}
bytes += res;
}
close(pipe_fd);
} else {
exit(EXIT_FAILURE);
}
printf("Process %d finish\n", getpid());
exit(EXIT_SUCCESS);
}
testmf.c
#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) {
printf("FIFO created \n");
}
exit(EXIT_SUCCESS);
}
listargs.c
#include <stdio.h>
main( int ac, char *av[] )
{
int i;
printf("Number of args: %d, Args are:\n", ac);
for(i=0;i<ac;i++)
printf("args[%d] %s\n", i, av[i]);
fprintf(stderr,"This message is sent to stderr.\n");
}
分析:
打印用户输入的指令,输出相关信息。
功能:
打印指令
pipe.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define oops(m,x) { perror(m); exit(x); }
int main(int ac, char **av)
{
int thepipe[2],
newfd,
pid;
if ( ac != 3 ){
fprintf(stderr, "usage: pipe cmd1 cmd2\n");
exit(1);
}
if ( pipe( thepipe ) == -1 )
oops("Cannot get a pipe", 1);
if ( (pid = fork()) == -1 )
oops("Cannot fork", 2);
if ( pid > 0 ){
close(thepipe[1]);
if ( dup2(thepipe[0], 0) == -1 )
oops("could not redirect stdin",3);
close(thepipe[0]);
execlp( av[2], av[2], NULL);
oops(av[2], 4);
}
close(thepipe[0]);
if ( dup2(thepipe[1], 1) == -1 )
oops("could not redirect stdout", 4);
close(thepipe[1]);
execlp( av[1], av[1], NULL);
oops(av[1], 5);
}
功能:
实现管道的功能
分析:
相当于管道的作用
pipedemo.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
int len, i, apipe[2];
char buf[BUFSIZ];
if ( pipe ( apipe ) == -1 ){
perror("could not make pipe");
exit(1);
}
printf("Got a pipe! It is file descriptors: { %d %d }\n",
apipe[0], apipe[1]);
while ( fgets(buf, BUFSIZ, stdin) ){
len = strlen( buf );
if ( write( apipe[1], buf, len) != len ){
perror("writing to pipe");
break;
}
for ( i = 0 ; i<len ; i++ )
buf[i] = 'X' ;
len = read( apipe[0], buf, BUFSIZ ) ;
if ( len == -1 ){
perror("reading from pipe");
break;
}
if ( write( 1 , buf, len ) != len ){
perror("writing to stdout");
break;
}
}
}
功能:
将输入输出用管道连接
分析:
我猜测是打开文件,将从标准输入中输入的数据打印到标准输出上,需要强制退出。
pipedemo2.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define CHILD_MESS "I want a cookie\n"
#define PAR_MESS "testing..\n"
#define oops(m,x) { perror(m); exit(x); }
main()
{
int pipefd[2];
int len;
char buf[BUFSIZ];
int read_len;
if ( pipe( pipefd ) == -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 );
}
}
}
分析:
从上图中我们可以分析出:fork函数执行后,进程先执行父进程,父进程休眠1s,接着子进程运行;子进程打印"I want a cookie",接着休眠5s,父进程接着打印,所以出现4次testing..之后又出现子进程的打印语句。
stdinredir1.c
#include <stdio.h>
#include <fcntl.h>
int main()
{
int fd ;
char line[100];
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
close(0);
fd = open("/etc/passwd", O_RDONLY);
if ( fd != 0 ){
fprintf(stderr,"Could not open data as fd 0\n");
exit(1);
}
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
}
分析:
如上图所示:先从标准输入输入3行信息,接着分别打印这三行信息,执行打开文件语句,若打开正常,则从文件中读取前三行信息。
stdinredir2.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
//#define CLOSE_DUP
//#define USE_DUP2
main()
{
int fd ;
int newfd;
char line[100];
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
fd = open("data", O_RDONLY);
#ifdef CLOSE_DUP
close(0);
newfd = dup(fd);
#else
newfd = dup2(fd,0);
#endif
if ( newfd != 0 ){
fprintf(stderr,"Could not duplicate fd to 0\n");
exit(1);
}
close(fd);
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
fgets( line, 100, stdin ); printf("%s", line );
}
testtty.c
#include <unistd.h>
int main()
{
char *buf = "abcde\n";
write(0, buf, 6);
}
分析:
将缓冲区中的内容打印出来
whotofile.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
int pid ;
int fd;
printf("About to run who into a file\n");
if( (pid = fork() ) == -1 ){
perror("fork"); exit(1);
}
if ( pid == 0 ){
close(1); /* close, */
fd = creat( "userlist", 0644 ); /* then open */
execlp( "who", "who", NULL ); /* and run */
perror("execlp");
exit(1);
}
if ( pid != 0 ){
wait(NULL);
printf("Done running who. results in userlist\n");
}
return 0;
}
分析:
定义函数:int close(int fd);
函数说明:当使用完文件后若已不再需要则可使用 close()关闭该文件, 二close()会让数据写回磁盘, 并释放该文件所占用的资源. 参数fd 为先前由open()或creat()所返回的文件描述词.
返回值:若文件顺利关闭则返回0, 发生错误时返回-1.
从结果中我们可以看出,没有子进程的执行,我们知道fork函数会产生2个返回,所以子进程是一定有的,close(1)关闭了子进程的标准输出,所以之后的执行都无法打印出来。
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);
printf("input: %s", x);
}
return 0;
}
void inthandler(int s)
{
printf("Called with signal %d\n", s);
sleep(s * 4);
printf("done handling signal %d\n", s);
}
分析:
从上图的结果中我们可以看到,该代码的功能是,将标准输入的信息打印到标准输出上,需要强制退出结束进程。
sigactdemo2.c
#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\n" );
}
return 0;
}
分析:
从上图中可以看出,该代码的功能是每2s打印"Two seconds passed"。
sigdemo1.c
#include <stdio.h>
#include <signal.h>
void f(int);
int main()
{
int i;
signal( SIGINT, f );
for(i=0; i<5; i++ ){
printf("hello\n");
sleep(2);
}
return 0;
}
void f(int signum)
{
printf("OUCH!\n");
}
分析:
每隔2s打印一次hello。
sigdemo2.c
#include <stdio.h>
#include <signal.h>
main()
{
signal( SIGINT, SIG_IGN );
printf("you can't stop me!\n");
while( 1 )
{
sleep(1);
printf("haha\n");
}
}
分析:
每隔1s打印一次"haha"。
sigdemo3.c
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#define INPUTLEN 100
int main(int argc, char *argv[])
{
void inthandler(int);
void quithandler(int);
char input[INPUTLEN];
int nchars;
signal(SIGINT, inthandler);//^C
signal(SIGQUIT, quithandler);//^\
do {
printf("\nType a message\n");
nchars = read(0, input, (INPUTLEN - 1));
if (nchars == -1)
perror("read returned an error");
else {
input[nchars] = '\0';
printf("You typed: %s", input);
}
}
while (strncmp(input, "quit", 4) != 0);
return 0;
}
void inthandler(int s)
{
printf(" Received signal %d .. waiting\n", s);
sleep(2);
printf(" Leaving inthandler \n");
}
void quithandler(int s)
{
printf(" Received signal %d .. waiting\n", s);
sleep(3);
printf(" Leaving quithandler \n");
}
分析:
从上图看出,我们从标准输入输入消息,在标准输出上打印出来。
三、本周代码托管截图
四、感悟体会
本周的代码很多,不过收获还是蛮大的,参考学姐以及其他同学的博客,通过实践一步步运行出结果,分析理解代码的含义。这样学习感觉很充实,希望自己可以再接再厉,学到更多有用的知识。
五、学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 0/0 | 1/2 | 20/30 | 学习了一些Linux核心命令 |
第二周 | 250/250 | 1/3 | 20/50 | 学习了vim编辑器基础 |
第三周 | 280/530 | 1/4 | 18/68 | 熟练在vim里编程并运行 |
第五周 | 300/830 | 1/5 | 15/83 | 学会生成汇编文件并查看 |
第六周 | 200/1030 | 1/6 | 12/95 | 了解Y86指令 |
第七周 | 139/1169 | 1/7 | 10/105 | 了解存储器层次结构 |
第八周 | 0/1169 | 2/9 | 8/113 | 复习前面所学内容 |
第九周 | 182/1351 | 2/11 | 9/122 | 了解Unix I/O |
第十周 | 502/1853 | 2/13 | 8/130 | 对代码进行理解 |
第十一周 | 1109/2962 | 3/16 | 10/140 | 对代码进行理解,学习第八章 |