20145302张薇 《信息安全系统设计基础》第11周学习总结
学习内容总结
- 本周主要学习《深入理解计算机系统》第八章 异常控制流实践的内容
8.1异常
1、异常是异常控制流的一种形式,它一部分是由硬件实现的,一部分是有操作系统实现的。
2、异常:控制流中的突变,用来响应处理器状态中的某些变化。
3、在处理器中,状态被编码为不同的位和信号。状态变化成为事件。
4、异常表:当处理器监测到有时间发生时,通过一张叫做异常表的跳转表,进行一个间接过程调用,到一个专门设计用来处理这类事件的操作系统子程序(异常处理程序)。
5、当异常处理程序完成处理后,根据引起异常的事件的类型,会发生以下三种情况的一种:
(1)处理程序将控制返回给当前指令Icurr,即当事件发生时正在执行的指令。
(2)处理程序将控制返回给Inext,即如果没有发生异常将会执行的下一条指令。
(3)处理程序终止被中断的程序。
8.1.1 异常处理
1、系统中可能的每种类型的异常都分配了一个唯一的非负整数的异常号。
异常号的分配: (1)处理器的设计者:被除零、缺页、存储器访问违例、断点以及算数溢出。
(2)操作系统内核的设计者分配的:系统调用和来自意外不I/O设备的信号。
2、异常号:到异常表中的索引
异常表基址寄存器:异常表的起始地址存放的位置。
3、异常与过程调用的异同:
(1)过程调用时,在跳转到处理器之前,处理器将返回地址压入栈中。然而,根据异常的类型,返回地址要么是当前指令,要么是下一条指令。
(2)处理器把一些额外的处理器状态压入栈里,在处理程序返回时,重新开始被中断的程序会需要这些状态。
(3)如果控制从一个用户程序转移到内核,那么所有这些项目都被压到内核栈中,而不是压到用户栈中。
(4)异常处理程序运行在内核模式下,意味着它们对所有的系统资源都有完全的访问权限。
8.1.2 异常的类别
1、异常的分类:中断、陷阱、故障和终止。
2、中断:异步发生,是来自处理器外部的I/O设备的信号的结果。
(1)硬件异常中断处理程序通常称为中断处理程序。
(2)异步异常是有处理器外部的I/O设备中的时间产生的,同步异常是执行一条指令的直接产物。
(3)陷阱、故障、终止时同步发生的,是执行当前指令的结果,我们把这类指令叫做故障指令。
3、陷阱和系统调用
(1)陷阱最重要的用途是在用户程序和内核之间提供一个像过程一样的接口,叫做系统调用。
(2)普通的函数运行在用户模式中,用户模式限制了函数可以执行的指令的类型,而且它们只能访问与调用函数相同的栈。系统调用运行在内核模式中,内核模式允许系统调用执行指令,并访问定义在内核中的栈。
4、故障:是由错误情况引起的。
例如:abort例程会终止引起故障的应用程序。 根据故障是否能够被修复,故障处理程序要么重新执行引起故障的指令,要么终止。例如:缺页故障。
5、终止:是不可恢复的致命错误造成的结果,通常是一些硬件错误。终止处理程序从不将控制返回给应用程序。
8.1.3 linux/IA32系统中的异常
1、031号:由intel架构师定义的异常。32255号:操作系统定义的中断和陷阱。
2、linux/IA32故障和终止:
(1)除法错误(linux中称为浮点异常)
(2)一般保护故障(linux中称为段故障)
(3)缺页
(4)机器检查
3、linux/IA32系统调用
- 每一个系统调用都有一个唯一的整数号,对应于一个到内核中跳转表的偏移量。
- C程序用syscall函数可以直接调用任何系统调用。
- 系统级函数:系统调用和它们相关联的包装函数。
- linux系统调用的参数都是通过吉春器而不是栈传递的,寄存器%eax包含系统调用号,栈指针%esp不能使用,因为当进入内核调用时,内核会覆盖它。
8.2 进程
1、异常是允许操作系统提供进程的概念所需要的基本构造块。
- 进程:一个执行中的程序的实例。
- 上下文是由程序正确运行所需要的状态组成的,这个状态包括存放在存储器中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
2、进程提供给应用程序的关键抽象: - 一个独立的逻辑控制流,独占地使用处理器;
- 一个私有的地址空间,独占地使用存储器系统。
8.2.1 逻辑控制流
- 程序计数器:唯一的对应于包含在程序的可执行目标文件中的指令,或者是包含在运行时动态链接到程序的共享对象中的指令。
- 这个PC值的序列叫做逻辑控制流,简称逻辑流。
8.2.2 并非流
1、- 并发流:一个逻辑流的执行在时间上与另一个流重叠。
- 并发:多个流并发地执行的一般现象。
- 多任务:一个进程和其他进程轮流运行的概念。
- 时间片:一个进程执行它的控制流的一部分的每一时间段。
多任务也叫时间分片。
2、并行流:如果两个流并发的运行在不同的处理器核或者计算机上
8.2.3 私有地址空间
- 和这个空间中某个地址相关联的那个存储器字节是不能被其他进程读或者写的。所以这个空间地址是私有的。
8.2.4 用户模式和内核模式
1、模式位:用某个控制寄存器中的一个位模式,限制一个应用可以执行的指令以及它可以访问的地址空间范围。
2、当设置了位模式,进程就运行在内核模式中,一个运行在内核模式中的进程可以中兴指令集中的任何指令,而且可以访问系统中任何存储器位置。
3、没有设置位模式时,进程就运行在用户模式中,不允许执行特权指令,例如停止处理器、改变位模式,或者发起一个I/O操作。
4、用户程序必须通过系统调用接口间接的当问内核代码和数据。
5、进程从用户模式变为内核模式的唯一方法是通过诸如中断、故障、或者陷入系统调用这样的异常。
上下文切换
1、上下文就是内核重新启动一个被抢占的进程所需的状态。
2、调度:内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程。有内核中称为调度器的代码处理的。
3、上下文切换机制:
(1)保存当前进程的上下文
(2)恢复某个先前被抢占的进程被保存的上下文
(3)将控制传递给这个新恢复的进程
4、引起上下文切换的情况
(1)当内核代表用户执行系统调用时
(2)中断时
8.3 系统调用错误处理
- 错误处理包装函数:包装函数调用基本函数,检查错误,如果有任何问题就终止。
8.4.1 获取进程ID
1、每个进程都有一个唯一的正数的进程ID。
2、getpid函数返回调用进程的PID,getppid函数返回它的父进程的PID。上面两个函数返回一个同类型为pid_t的整数值,在linux系统中,它在types.h中被定义为int。
8.4.2 创建和终止进程
1、进程总处于三种状态
(1)运行:进程要么在CPU上执行,要么在等待被执行且最终会被内核调度。
(2)停止:程序的执行被挂起,,且不会被调度。
(3)终止:进程用永远停止了。终止原因:
- 收到一个信号,默认行为是终止进程;
- 从主进程返回
- 调用exit函数。
2、父进程通过调用fork函数创建一个新的运行的子进程。
3、子进程和父进程的异同:
异:有不同的PID
同:用户级虚拟地址空间,包括:文本、数据和bss段、堆以及用户栈。任何打开文件描述符,子进程可以读写父进程中打开的任何文件。
4、fork函数: 因为父进程的PID总是非零的,返回值就提供一个明确的方法来分辨程序是在父进程还是在子进程中执行。
fork函数的特点:
(1)调用一次,返回两次
(2)并发执行
(3)相同的但是独立的地址空间
(4)共享文件
8.4.3 回收子进程
1、当父进程回收已终止的子进程时,内核将子进程的退出状态传递给父进程,然后抛弃已终止的进程。
一个终止了但还未被回收的进程称为僵死进程。
2、一个进程可以通过调用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挂起调用进程的执行,直到它的等待集合中的一个子进程终止。
3、判定等待集合的成员
有参数pid来确定的:
(1)pid>0:等待集合是一个单独的子进程,进程ID等于pid。
(2)pid=-1:等待结合就是由父进程所有的子进程组成的。
4、修改默认行为 - 通过options设置:
(1)WNOHANG:默认行为是挂起调用进程。
(2)WUNTRACED:默认行为是只返回已终止的子进程。
(3)WNOHANG|WUNTRACED:立即返回,如果等待集合中没有任何子进程被停止或者已终止,那么返回值为0,或者返回值等于那个被停止或者已经终止的子进程的PID。
5、检查已回收子进程的退出状态
wait.h头文件定义了解释status参数的几个宏:
(1)WIFEXITED:如果子进程通过调用exit或者一个返回正常终止,就返回真;
(2)WEXITSTATUS:返回一个正常终止的子进程的退出状态。只有在WIFEXITED返回真时,才会定义这个状态。
6、错误条件
(1)若调用进程没有子进程,那么waitpid返回-1,并且设置errno为ECHILD;
(2)若waitpid函数被一个信号中断,那么返回-1,并设置errno为EINTR
7、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 让进程休眠
1、sleep函数:将进程挂起一段指定的时间
#include <unistd.h>
unsigned int sleep(unsigned int secs);
//返回:还要休眠的秒数
- 如果请求的时间量已经到了,返回0,否则返回还剩下的要休眠的秒数。
2、pause函数:让调用函数休眠,直到该进程收到一个信号。
#include <unistd.h>
int pause(void);
//返回:总是-1
8.4.5 加载并运行程序
1、execve函数:在当前进程的上下文中加载并运行一个新程序。
#include <unistd.h>
int execve(const char *filename,const char *argv[],const char *envp[]);
//返回:若成功,则不返回,若错误,返回-1
- filename:可执行目标文件
- argv:带参数列表
- envp:环境变量列表
- 特点:execve调用一次从不返回
2、getenv函数:在环境数组中搜素字符串“name =VALUE”,若找到了,就返回一个指向value的指针,否则它就返回NULL。
#include <stdlib.h>
char *getenv(const char *name);
//返回:存在,返回指向name的指针,若无匹配的,为NULL
3、注意:
- execve函数在当前进程的上下文中加载并运行一个新的进程。它会覆盖当前进程的地址空间,并没有创建一个新的进程,新的进程仍然有相同的PID,并且继承了调用execve函数时已打开的所有文件描述符。
8.4.6 利用fork和execve运行程序
1、外壳是一个交互型的应用级程序,它代表用户运行其他程序。
2、外壳执行一系统的读/求值步骤,然后终止。读步骤读取来自用户的一个命令行,求值步骤解释命令行,并代表用户运行程序。
3、eval函数:对外壳命令行求值
4、parseline函数:解析外壳的一个输入
8.5 信号
- 底层的硬件异常是由内核异常处理程序处理的,正常情况下,对用户进程而言是不可见的。
- 其他信号对应于内核或者其他用户进程中较高层的软件事件。
8.5.1 信号术语
1、发送信号的两个不同步骤:
(1)发送信号:内核通过更新目的进程上下文中的某个状态,发送(递送)一个信号给目的进程。
发送信号的两个原因:
(1)内核监测到一个系统事件,比如被零除错误或者子进程终止。
(2)一个进程调用了kill函数,显式地要求内核发送一个信号给目的进程。一个进程可以发送信号给它自己。
(2)接收信号:信号处理程序捕获信号的基本思想。
2、待处理信号:一个只发出而没有被接收的信号
- 一个进程可以有选择性地阻塞接收某种信号。
- 待处理信号不会被接收,直到进程取消对这种信号的阻塞。
3、一个待处理信号最多只能被接受一次,pending位向量:维护着待处理信号集合,blocked向量:维护着被阻塞的信号集合。
8.5.2 发送信号
1、进程组
每个进程都只属于一个进程组,进程组是由一个正整数进程组ID来标识的。getpgrp函数返回当前进程的进程组ID:默认地,一个子进程和它的父进程同属于一个进程组。
2、用/bin/kill/程序发送信号 一个为负的PID会导致信号被发送到进程组PID中的每个进程。
3、从键盘发送信号
作业:表示对一个命令行求值而创建的进程。外壳为每个作业创建一个独立的进程组。
4、用kill函数发送信号
进程通过调用kill函数发送信号给其他的进程。父进程用kill函数发送SIGKILL信号给它的子进程。
5、用alarm函数发送信号
在任何情况下,对alarm的调用都将取消任何待处理的闹钟,并且返回任何待处理的闹钟在被发送前还剩下的秒数。
8.5.3 接收信号
1、当内核从一个异常处理程序返回,准备将控制传递该进程p时,它会检查进程p的未被阻塞的待处理信号的集合。如果这个集合是非空的,那么内核选择集合中的某个信号k,并且强制p接收信号k。
2、进程可以通过使用signal函数修改和信号相关联的默认行为。 唯一例外是SIGSTOP和SIGKILL,它们的默认行为是不能被修改的。
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler);
//返回:若成功,返回指向前次处理程序的指针;若出错,为SIG_ERR
3、signal函数改变和信号signum相关联的行为的三种方法:
(1)handler是SIG_ IGN,忽略类型为signum的信号;
(2)handler是SIG_ DFL,类型为signum的信号行为恢复为默认行为。 (3)否则,handler就是用户定义的函数地址。这个函数称为信号处理程序。
4、设置信号处理程序:通过把处理程序的地址传递到signal函数从而改变默认行为。
- 捕获信号:调用信号处理程序。
- 处理信号:执行信号处理程序。
5、因为信号处理程序的逻辑控制流与主函数的逻辑控制流重叠,信号处理程序和主函数并发执行。
8.5.4 信号处理问题
1、待处理信号被阻塞:
2、待处理信号不会排队等待;
3、系统调用可以被中断:像read、wait、accept这样的系统调用潜在地会阻塞进程一段较长的时间,称为慢速系统调用。
- 注意:不可以用信号来对其他进程中发生的事件计数。
8.5.5 可移植的信号处理
- 信号处理语义的差异,是UNIX信号处理的一个缺陷。
8.5.6 显式地阻塞和取消阻塞信号
- sigprocmask函数改变当前已阻塞信号的信号。
- how的值:
(1)SIG_ BLOCK :添加set中的信号到blocked中
(2)SIG_ UNBLOCK:从blocked中删除set中的信号
(3)SIG_ SETMASK:blocked = set
实践部分
1.sigactdemo
#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
", s);
sleep(s * 4);
printf("done handling signal %d
", s);
}
- 参数结构sigaction定义如下
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
}
- flag
- SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL
- SA_RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用
- SA_NODEFER :一般情况下, 当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置SA_NODEFER标记, 那么在该信号处理函数运行时,内核将不会阻塞该信号
- 函数sigaction
int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact);
- sigaction()会依参数signum指定的信号编号来设置该信号的处理函数。参数signum可以指定SIGKILL和SIGSTOP以外的所有信号。
2.sigactdemo2
#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;
}
- 每两秒输出一次
3.sigdemo1
#include <stdio.h>
#include <signal.h>
void f(int);
int main()
{
int i;
signal( SIGINT, f );
for(i=0; i<5; i++ ){
printf("hello
");
sleep(2);
}
return 0;
}
void f(int signum)
{
printf("OUCH!
");
}
- 连续输出五个hello,每两个间隔是两秒
- 在这期间,每次输入的
Ctrl+C
都被处理成打印OUCH
4.sigdemo2
#include <stdio.h>
#include <signal.h>
main()
{
signal( SIGINT, SIG_IGN );
printf("you can't stop me!
");
while( 1 )
{
sleep(1);
printf("haha
");
}
}
- 一直输出haha,按
Ctrl+C
不能停止。 - SIG_DFL,SIG_IGN 分别表示无返回值的函数指针,指针值分别是0和1,这两个指针值逻辑上讲是实际程序中不可能出现的函数地址值。
- SIG_DFL:默认信号处理程序
- SIG_IGN:忽略信号的处理程序
5.sigdemo3
#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("
Type a message
");
nchars = read(0, input, (INPUTLEN - 1));
if (nchars == -1)
perror("read returned an error");
else {
input[nchars] = ' ';
printf("You typed: %s", input);
}
}
while (strncmp(input, "quit", 4) != 0);
return 0;
}
void inthandler(int s)
{
printf(" Received signal %d .. waiting
", s);
sleep(2);
printf(" Leaving inthandler
");
}
void quithandler(int s)
{
printf(" Received signal %d .. waiting
", s);
sleep(3);
printf(" Leaving quithandler
");
}
- 多信号处理SIGX打断SIGX的情况
- 递归,调用同一个处理函数
- 忽略第二给信号
- 阻塞第二个信号直到第一个处理完毕
6.exec1
#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;
}
ok
int execvp(const char file ,char const argv []);
- execvp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名,找到后便执行该文件,然后将第二个参数argv传给该欲执行的文件。
- 如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中。
- 在执行时exevp函数调用成功没有返回,所以没有打印“* * * ls is done. bye”
7.exec2
#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
");
execvp( arglist[0] , arglist );
printf("* * * ls is done. bye
");
}
- exec1传的是ls,exec2传送的是arglist[0],但运行结果是相同的。
8.exer3
#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
");
execlp("ls", "ls", "-l", NULL);
printf("* * * ls is done. bye
");
}
- int execlp(const char * file,const char * arg,....);
- execlp()会从PATH 环境变量所指的目录中查找符合参数file的文件名,找到后便执行该文件,然后将第二个以后的参数当做该文件的argv[0]、argv[1]……,最后一个参数必须用空指针(NULL)作结束。
- 指定了环境变量,然后依然执行了ls -l指令,成功后没有返回,所以最后一句话不会输出。运行结果同exec1。
9.forkdemo1
#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
", mypid);
ret_from_fork = fork();
sleep(1);
printf("After: my pid is %d, fork() said %d
",
getpid(), ret_from_fork);
return 0;
}
- 这个代码先是打印进程pid,然后调用fork函数生成子进程,休眠一秒后再次打印进程id,这时父进程打印子进程pid,子进程返回0。
- 父进程通过调用fork函数创建一个新的运行子进程。
- 调用一次,返回两次。一次返回到父进程,一次返回到新创建的子进程。
10.forkdemo2
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("before:my pid is %d
", getpid() );
fork();
fork();
printf("aftre:my pid is %d
", getpid() );
return 0;
}
- 这个代码调用两次fork,一共产生四个子进程,所以会打印四个aftre输出。
11.forkdemo4
#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;
}
- 先打印进程pid,然后fork创建子进程,父进程返回子进程pid,所以输出parent一句,休眠十秒;子进程返回0,所以输出child与之后一句。
12.forkgdb
#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
", li++);
sleep(1);
printf("child gi:%d
", gi++);
printf("child si:%d
", si++);
}
exit(0);
}
else{
for(i=0; i<5; i++){
printf("parent li:%d
", li++);
printf("parent gi:%d
", gi++);
sleep(1);
printf("parent si:%d
", si++);
}
exit(0);
}
return 0;
}
- 父进程打印是先打印两句,然后休眠一秒,然后打印一句,子进程先打印一句,然后休眠一秒,然后打印两句。并且这两个线程是并发的,所以可以看到在一个线程休眠的那一秒,另一个线程在执行,并且线程之间相互独立互不干扰。
13.psh1
#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;
}
- 依次你输入要执行的指令与参数,回车表示输入结束,然后输入的每个参数对应到函数中,再调用对应的指令。
- 第一个是程序名,然后依次是程序参数。
- 一个字符串,一个字符串构造参数列表argist,最后在数组末尾加上NULL
- 将arglist[0]和arglist数组传给execvp。
- 程序正常运行,execvp命令指定的程序代码覆盖了shell程序代码,并在命令结束之后退出,shell就不能再接受新的命令。
14.psh2
#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] = ' ';
cp = malloc( strlen(buf)+1 );
if ( cp == NULL ){
fprintf(stderr,"no memory
");
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
",
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 != '
' )
arglist[numargs++] = makestring(argbuf);
else
{
if ( numargs > 0 ){
arglist[numargs]=NULL;
execute( arglist );
numargs = 0;
}
}
}
return 0;
}
- 比起psh1多了循环判断,不退出的话就可以一直保持在输入指令,并且对于子程序存在的状态条件。
- 为了解决这个问题,程序通过调用fork来复制自己。
- 调用fork函数之后内核的工作过程:
分配新的内存块和内核数据结构
复制原来的进程到新的进程
向运行进程集添加新的进程
将控制返回给两个进程
15.testbuf1
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("hello");
fflush(stdout);
while(1);
}
- 效果是先输出hello,然后保持在循环中不结束进程。
16.testbuf2
#include <stdio.h>
int main()
{
printf("hello
");
while(1);
}
- fflush(stdout)的效果和换行符 是一样的。
17.testbuf3
#include <stdio.h>
int main()
{
fprintf(stdout, "1234", 5);
fprintf(stderr, "abcd", 4);
}
- 将内容格式化输出到标准错误、输出流中。
18.testpid
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
printf("my pid: %d
", getpid());
printf("my parent's pid: %d
", getppid());
return 0;
}
- 输出当前进程pid和当前进程的父进程的pid。
19.testpp
#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;
}
20.testsystem
#include <stdlib.h>
int main ( int argc, char *argv[] )
{
system(argv[1]);
system(argv[2]);
return EXIT_SUCCESS;
} /* ---------- end of function main ---------- */
- system()——执行shell命令,也就是向dos发送一条指令。这里是后面可以跟两个参数,然后向dos发送这两个命令,分别执行。
21.waitdemo1
#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
", getpid(), delay);
sleep(delay);
printf("child done. about to exit
");
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
",
childpid, wait_rv);
}
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);
return 0;
}
- 如果有子进程,则终止子进程,成功返回子进程pid。
22.waitdemo2
#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);
}
- 多了一个子进程的状态区分,把状态拆分成三块,exit,sig和core。
其他
- 本周按照代码驱动学习将自己的git目录重新更新了。