20145215《信息安全系统设计基础》第十一周学习总结
20145215《信息安全系统设计基础》第十一周学习总结
教材学习内容总结
异常
-
异常是异常控制流的一种形式,它一部分是由硬件实现的,一部分是有操作系统实现的。
-
异常:控制流中的突变,用来响应处理器状态中的某些变化。
-
在处理器中,状态被编码为不同的位和信号。状态变化成为事件。
-
异常表:当处理器监测到有时间发生时,通过一张叫做异常表的跳转表,进行一个间接过程调用,到一个专门设计用来处理这类事件的操作系统子程序(异常处理程序)。
-
当异常处理程序完成处理后,根据引起异常的事件的类型,会发生以下三种情况的一种:
- 处理程序将控制返回给当前指令Icurr,即当事件发生时正在执行的指令。
- 处理程序将控制返回给Inext,即如果没有发生异常将会执行的下一条指令。
- 处理程序终止被中断的程序。
-
异常号:到异常表中的索引
-
异常表基址寄存器:异常表的起始地址存放的位置。
-
异常与过程调用的异同:
- 过程调用时,在跳转到处理器之前,处理器将返回地址压入栈中。然而,根据异常的类型,返回地址要么是当前指令,要么是下一条指令。
- 处理器把一些额外的处理器状态压入栈里,在处理程序返回时,重新开始被中断的程序会需要这些状态。
- 如果控制从一个用户程序转移到内核,那么所有这些项目都被压到内核栈中,而不是压到用户栈中。
- 异常处理程序运行在内核模式下,意味着它们对所有的系统资源都有完全的访问权限。
异常的类别
-
异常的分类:中断、陷阱、故障和终止。
-
中断:异步发生,是来自处理器外部的I/O设备的信号的结果。 硬件异常中断处理程序通常称为中断处理程序。
-
陷阱和系统调用:
- 陷阱最重要的用途是在用户程序和内核之间提供一个像过程一样的接口,叫做系统调用。
- 普通的函数运行在用户模式中,用户模式限制了函数可以执行的指令的类型,而且它们只能访问与调用函数相同的栈。系统调用运行在内核模式中,内核模式允许系统调用执行指令,并访问定义在内核中的栈。
-
故障:是由错误情况引起的。
-
终止:是不可恢复的致命错误造成的结果,通常是一些硬件错误。终止处理程序从不将控制返回给应用程序。
-
031号:由intel架构师定义的异常;32255号:操作系统定义的中断和陷阱。
-
每一个系统调用都有一个唯一的整数号,对应于一个到内核中跳转表的偏移量。
进程
-
异常是允许操作系统提供进程的概念所需要的基本构造块。
-
进程:一个执行中的程序的实例。
-
上下文是由程序正确运行所需要的状态组成的,这个状态包括存放在存储器中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
-
进程提供给应用程序的关键抽象:
- 一个独立的逻辑控制流,独占地使用处理器;
- 一个私有的地址空间,独占地使用存储器系统。
-
并发流:一个逻辑流的执行在时间上与另一个流重叠。
-
并发:多个流并发地执行的一般现象。
-
多任务:一个进程和其他进程轮流运行的概念。
-
时间片:一个进程执行它的控制流的一部分的每一时间段。
-
并行流:如果两个流并发的运行在不同的处理器核或者计算机上
用户模式和内核模式
- 模式位:用某个控制寄存器中的一个位模式,限制一个应用可以执行的指令以及它可以访问的地址空间范围。
- 当设置了位模式,进程就运行在内核模式中,一个运行在内核模式中的进程可以中兴指令集中的任何指令,而且可以访问系统中任何存储器位置。
- 没有设置位模式时,进程就运行在用户模式中,不允许执行特权指令,例如停止处理器、改变位模式,或者发起一个I/O操作。
- 用户程序必须通过系统调用接口间接的当问内核代码和数据。
- 进程从用户模式变为内核模式的唯一方法是通过诸如中断、故障、或者陷入系统调用这样的异常。
上下文切换
-
上下文就是内核重新启动一个被抢占的进程所需的状态。
-
调度:内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程。有内核中称为调度器的代码处理的。
-
上下文切换机制:
- 保存当前进程的上下文
- 恢复某个先前被抢占的进程被保存的上下文
- 将控制传递给这个新恢复的进程
-
引起上下文切换的情况
- 当内核代表用户执行系统调用时
- 中断时
系统调用错误处理
- 错误处理包装函数:包装函数调用基本函数,检查错误,如果有任何问题就终止。
进程控制
获取进程ID
- 每个进程都有一个唯一的正数的进程ID。
- getpid函数返回调用进程的PID,getppid函数返回它的父进程的PID。上面两个函数返回一个同类型为pid_t的整数值,在linux系统中,它在types.h中被定义为int。
创建和终止进程
-
进程总处于三种状态
- 运行:进程要么在CPU上执行,要么在等待被执行且最终会被内核调度。
- 停止:程序的执行被挂起,,且不会被调度。
- 终止:进程用永远停止了。终止原因:(1)收到一个信号,默认行为是终止进程;(2)从主进程返回(3)调用exit函数。
-
父进程通过调用fork函数创建一个新的运行的子进程。
-
子进程和父进程的异同:
- 异:有不同的PID
- 同:用户级虚拟地址空间,包括:文本、数据和bss段、堆以及用户栈。任何打开文件描述符,子进程可以读写父进程中打开的任何文件。
-
fork函数: 因为父进程的PID总是非零的,返回值就提供一个明确的方法来分辨程序是在父进程还是在子进程中执行。
fork函数的特点:- 调用一次,返回两次
- 并发执行
- 相同的但是独立的地址空间
- 共享文件
回收子进程
- 当父进程回收已终止的子进程时,内核将子进程的退出状态传递给父进程,然后抛弃已终止的进程。
一个终止了但还未被回收的进程称为僵死进程。 - 一个进程可以通过调用waitpid函数来等待它的子进程终止或者停止。
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid,int *status,int options);
//返回:若成功,返回子进程的PID;若WNOHANG,返回0;若其他错误,返回-1
-
修改默认行为,通过options设置:
- WNOHANG:默认行为是挂起调用进程。
- WUNTRACED:默认行为是只返回已终止的子进程。
- WNOHANG|WUNTRACED:立即返回,如果等待集合中没有任何子进程被停止或者已终止,那么返回值为0,或者返回值等于那个被停止或者已经终止的子进程的PID。
-
错误条件:
- 若调用进程没有子进程,那么waitpid返回-1,并且设置errno为ECHILD;
- 若waitpid函数被一个信号中断,那么返回-1,并设置errno为EINTR
信号
信号术语
-
发送信号的两个不同步骤:
- 发送信号:内核通过更新目的进程上下文中的某个状态,发送(递送)一个信号给目的进程。
- 接收信号:信号处理程序捕获信号的基本思想。
-
发送信号的两个原因:
- 内核监测到一个系统事件,比如被零除错误或者子进程终止。
- 一个进程调用了kill函数,显式地要求内核发送一个信号给目的进程。一个进程可以发送信号给它自己。
-
待处理信号:一个只发出而没有被接收的信号
-
一个进程可以有选择性地阻塞接收某种信号。
待处理信号不会被接收,直到进程取消对这种信号的阻塞。 -
一个待处理信号最多只能被接受一次,pending位向量:维护着待处理信号集合,blocked向量:维护着被阻塞的信号集合。
发送信号
- 进程组:每个进程都只属于一个进程组,进程组是由一个正整数进程组ID来标识的。getpgrp函数返回当前进程的进程组ID:默认地,一个子进程和它的父进程同属于一个进程组。
- 用/bin/kill/程序发送信号:一个为负的PID会导致信号被发送到进程组PID中的每个进程。
- 从键盘发送信号:作业:表示对一个命令行求值而创建的进程。外壳为每个作业创建一个独立的进程组。
- 用kill函数发送信号: 进程通过调用kill函数发送信号给其他的进程。父进程用kill函数发送SIGKILL信号给它的子进程。
- 用alarm函数发送信号:在任何情况下,对alarm的调用都将取消任何待处理的闹钟,并且返回任何待处理的闹钟在被发送前还剩下的秒数。
接收信号
- 当内核从一个异常处理程序返回,准备将控制传递该进程p时,它会检查进程p的未被阻塞的待处理信号的集合。如果这个集合是非空的,那么内核选择集合中的某个信号k,并且强制p接收信号k。
- 进程可以通过使用signal函数修改和信号相关联的默认行为。 唯一例外是SIGSTOP和SIGKILL,它们的默认行为是不能被修改的。
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler);
//返回:若成功,返回指向前次处理程序的指针;若出错,为SIG_ERR
- signal函数改变和信号signum相关联的行为的三种方法:
- handler是SIG_ IGN,忽略类型为signum的信号;
- handler是SIG_ DFL,类型为signum的信号行为恢复为默认行为。
- 否则,handler就是用户定义的函数地址。这个函数称为信号处理程序。
- 设置信号处理程序:通过把处理程序的地址传递到signal函数从而改变默认行为。
- 捕获信号:调用信号处理程序。
- 处理信号:执行信号处理程序。
- 因为信号处理程序的逻辑控制流与主函数的逻辑控制流重叠,信号处理程序和主函数并发执行。
信号处理问题
- 待处理信号被阻塞:
- 待处理信号不会排队等待;
- 系统调用可以被中断:像read、wait、accept这样的系统调用潜在地会阻塞进程一段较长的时间,称为慢速系统调用。
注意:不可以用信号来对其他进程中发生的事件计数。
实践部分
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;
}
运行结果
分析过程
- execvp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名,找到后便执行该文件,然后将第二个参数argv传给该欲执行的文件。
- 如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中。
- 在执行时exevp函数调用成功没有返回,所以没有打印“* * * ls is done. bye”
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把“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");
execlp("ls", "ls", "-l", NULL);
printf("* * * ls is done. bye\n");
}
分析过程
-
查看帮助文档:
-
int execlp(const char * file,const char * arg,....);
-
execlp()会从PATH 环境变量所指的目录中查找符合参数file的文件名,找到后便执行该文件,然后将第二个以后的参数当做该文件的argv[0]、argv[1]……,最后一个参数必须用空指针(NULL)作结束。
-
指定了环境变量,然后依然执行了ls -l指令,成功后没有返回,所以最后一句话不会输出。运行结果同exec1。
exec函数族
- fork()函数通过系统调用创建一个与原来进程(父进程)几乎完全相同的进程,在fork后的子进程中使用exec函数族,可以装入和运行其它程序(子进程替换原有进程,和父进程做不同的事),fork创建一个新的进程就产生了一个新的PID,exec启动一个新程序,替换原有的进程,因此这个新的被exec执行的进程的PID不会改变。
- exec函数族装入并运行程序path/file,并将参数arg0(arg1, arg2, argv[], envp[])传递给子程序,出错返回-1。
- 在exec函数族中,后缀l、v、p、e指定函数将具有某种操作能力:
后缀 | 操作能力 |
---|---|
l | 希望接收以逗号分隔的参数列表,列表以NULL指针作为结束标志 |
v | 希望接收到一个以NULL结尾的字符串数组的指针 |
p | 是一个以NULL结尾的字符串数组指针,函数可以DOS的PATH变量查找子程序文件 |
e | 函数传递指定参数envp,允许改变子进程的环境,无后缀e时,子进程使用当前程序的环境 |
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;
}
运行结果
分析过程
- 这个代码先是打印进程pid,然后调用fork函数生成子进程,休眠一秒后再次打印进程id,这时父进程打印子进程pid,子进程返回0。
- 父进程通过调用fork函数创建一个新的运行子进程。
- 调用一次,返回两次。一次返回到父进程,一次返回到新创建的子进程。
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,一共产生四个子进程,所以会打印四个aftre输出。
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 4954”,又出现了一次“I am the parent. my child is 4954”。
- 这个代码进行了错误处理,提高了代码的健壮性。
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;
}
运行结果
分析过程
- 先打印进程pid,然后fork创建子进程,父进程返回子进程pid,所以输出parent一句,执行sleep(10)语句,休眠十秒。
- 子进程返回0,所以输出child与之后一句。
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;
}
运行结果
分析过程
- 父进程打印是先打印两句,然后休眠一秒,然后打印一句,子进程先打印一句,然后休眠一秒,然后打印两句。并且这两个线程是并发的,所以可以看到在一个线程休眠的那一秒,另一个线程在执行,并且线程之间相互独立互不干扰。
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;
}
运行结果
分析过程
- 依次你输入要执行的指令与参数,回车表示输入结束,然后输入的每个参数对应到函数中,再调用对应的指令。
- 第一个是程序名,然后依次是程序参数。
- 一个字符串,一个字符串构造参数列表argist,最后在数组末尾加上NULL
- 将arglist[0]和arglist数组传给execvp。
- 程序正常运行,execvp命令指定的程序代码覆盖了shell程序代码,并在命令结束之后退出,shell就不能再接受新的命令。
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,然后保持在循环中不结束进程。
testbuf2.c
程序代码
#include <stdio.h>
int main()
{
printf("hello\n");
while(1);
}
运行结果
和testbuf1.c运行结果一样
分析过程
- 从testbuf1.c和testbuf2.c的运行结果上看,我们可以猜出fflush(stdout);的功能就是打印换行符。
testbuf3.c
程序代码
#include <stdio.h>
int main()
{
fprintf(stdout, "1234", 5);
fprintf(stderr, "abcd", 4);
}
运行结果
分析过程
- 将内容格式化输出到标准错误、输出流中。
- 在默认情况下,stdout是行缓冲的,他的输出会放在一个buffer里面,只有到换行的时候,才会输出到屏幕。而stderr是无缓冲的,会直接输出,因此stderr中的值会先显示出来。
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;
}
运行结果
分析过程
- 功能:输出当前进程pid和当前进程的父进程的pid。
testpp.c
程序代码
#include <stdio.h>
#include <stdlib.h>
int main()
{
char **pp;
pp[0] = malloc(20);
return 0;
}
运行结果
testsystem.c
程序代码
#include <stdlib.h>
int main ( int argc, char *argv[] )
{
system(argv[1]);
system(argv[2]);
return EXIT_SUCCESS;
}
运行结果
分析过程
- 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;
}
运行结果
分析过程
- 如果有子进程,则终止子进程,成功返回子进程pid。
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);
}
运行结果
分析过程
- 多了一个子进程的状态区分,把状态拆分成三块,exit,sig和core。
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);
}
运行结果
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);
}
运行结果
分析过程
- 功能:相当于管道命令
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);
}
运行结果
分析过程
- 参数结构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以外的所有信号。
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;
}
运行结果
分析过程
- 每两秒输出一次
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");
}
运行结果
分析过程
- 连续输出五个hello,每两个间隔是两秒
- 在这期间,每次输入的Ctrl+C都被处理成打印OUCH
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");
}
}
运行结果
分析过程
- 一直输出haha,按Ctrl+C不能停止,必须按Ctrl+Z。
- SIG_DFL,SIG_IGN 分别表示无返回值的函数指针,指针值分别是0和1,这两个指针值逻辑上讲是实际程序中不可能出现的函数地址值。
- SIG_DFL:默认信号处理程序
- SIG_IGN:忽略信号的处理程序
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");
}
运行结果
分析过程
- 从上图看出,我们从标准输入输入消息,在标准输出上打印出来。
遇到的问题及解决过程
问题
- 在编译运行testpp.c代码时出现错误:
解决
- 我觉得应该是pp变量中保存的是一个无效随机不可用的地址,谁也不知道它指向哪里,因此用
char **pp
时,要给它分配一个内存地址。 - 对代码进行如下修改:
#include <stdio.h>
#include <stdlib.h>
int main()
{
char **pp;
pp = (char **) malloc(sizeof(char**));
pp[0] = malloc(20);
return 0;
}
- 这时再编译运行结果就没有问题了。
本周代码托管截图
- 代码托管链接:click here
- 代码行数统计:
心得体会
- 本周在学习了异常控制流的基础上,继续学习了系统调用,由此可见系统调用的重要性。
- 此外,本周学习的内容有点多,想要深入理解,还需要更多的时间去消化。
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 0/0 | 1/2 | 25/45 | 学习了几个Linux核心命令 |
第二周 | 55/55 | 2/4 | 27/72 | 学会了vim,gcc以及gdb的基本操作 |
第三周 | 148/203 | 1/5 | 23/95 | 对信息的表示和处理有更深入的理解 |
第五周 | 72/275 | 1/6 | 25/120 | 对汇编语言有了更深的理解 |
第六周 | 56/331 | 2/8 | 30/150 | 安装了Y86模拟器 |
第七周 | 61/392 | 1/9 | 22/172 | 理解了局部性原理和缓存思想在存储层次结构中的应用 |
第八周 | 0/392 | 1/10 | 20/192 | 复习前几章内容 |
第九周 | 132/524 | 2/12 | 24/216 | 了解了Linux操作系统提供的基本I/O服务 |
第十周 | 420/524 | 2/14 | 20/236 | 对常用指令的代码进行了分析调试,加深了理解 |
第十一周 | 1017/1541 | 2/16 | 26/262 | 对系统调用有了更深的认识 |