zoukankan      html  css  js  c++  java
  • 20145203 《信息安全系统设计基础》第十一周学习总结

    20145203 《信息安全系统设计基础》第十一周学习总结

    第八章 异常控制流

    教材学习内容总结

    绪论

    1、控制流类型

    • 平滑:指在存储器中指令都是相邻的。
    • 突变:出现不相邻指令(诸如跳转、调用、和78返回)。

    2、关于ECF:

    1.ECF是操作系统用来实现I/O、进程和虚拟存器的基本机制
    2.应用程序通过使用一个叫做陷阱或者系统调用的ECF形式,向操作系统请求服务
    3.ECF是计算机系统中实现并发的基本机制
    4.软件异常机制——C++和Java有try,catch,和throw,C中非本地跳转是setjmp和longjmp
    

    第一节 异常

    1、异常:是异常控制流的一种形式,由硬件操作系统实现。简单来说,就是控制流中的突变

    • 事件:即状态变化,与当前指令的执行可能直接相关,也可能没有关系。

    • 出现异常的处理方式:

        1.处理器检测到有异常发生
        2.通过异常表,进行间接过程调用,到达异常处理程序
        3.完成处理后:①返回给当前指令②返回给下一条指令③终止程序
      

    2、异常处理

    • 异常号:系统为每种类型的异常分配的唯一的非负整数
    • 异常表:系统启动时操作系统就会初始化一张跳转表,使得条目k包含异常k的处理程序的地址。
    • 两者关系:异常号是到异常表中的索引,异常表的起始地址放在异常表基址寄存器中。

    异常类似于过程调用,区别在:

    1.处理器压入栈的返回地址,是当前指令地址或者下一条指令地址。
    2.处理器也把一些额外的处理器状态压到栈里
    3.如果控制一个用户程序到内核,所有项目都压到内核栈里。
    4.异常处理程序运行在内核模式下,对所有的系统资源都有完全的访问权限。
    

    3、异常的类别

    (1)中断

    • 异步发生
    • 来自处理器外部的I/O设备的信号的结果
    • 硬件中断的异常处理程序通常称为中断处理程序
    • 总是返回下一条指令

    (2)陷阱

    • 陷阱是有意的异常,是执行一条指令的结果
    • 最重要的用途——系统调用:允许用户程序向内核服务进行受控的访问。
    • 总是返回下一条指令

    (3)故障

    • 由错误状况引起,可能能够被故障处理程序修正
    • 若被修正:重新执行指令(就是返回当前指令地址);未被修正:返回内核的abort例程,终止用户程序
    • 典型示例:缺页异常

    (4)终止

    • 是不可恢复的致命错误造成的结果,通常是一些硬件错误
    • 处理结果:返回内核的abort例程,终止用户程序

    4、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、进程的经典定义:一个执行中的程序的实例。

    系统中的每个程序都是运行在某个进程的上下文中的。

    上下文:由程序正确运行所需的状态组成的。

    进程提供给应用程序的关键抽象:

    • 一个独立的逻辑控制流:独占的使用处理器
    • 一个私有的地址空间:独占的使用存储器系统

    2、逻辑控制流

    (1)含义

    一系列的程序计数器PC的值,分别唯一的对应于包含在程序的可执行目标文件中的指令,或者是包含在运行时动态链接到程序的共享对象中的指令,这个PC值的序列就叫做逻辑控制流

    (2)工作(参见图书8-12)

    进程是轮流使用处理器的。每个进程执行它的流的一部分,然后被抢占(暂时挂起),然后轮到其他进程。但是进程可以向每个程序提供一种假象,好像它在独占的使用处理器。

    (3)逻辑流示例

    异常处理程序、进程、信号处理程序、线程、Java进程

    3、并发流

    (1)含义

    一个逻辑流的执行在时间上与另一个流重叠。这两个流并发的运行。(与流运行的处理器核数和计算机数无关)

    (2)几个概念

    • 并发:多个流并发的执行
    • 多任务:一个进程和其他进程轮流运行(也叫时间分片)
    • 时间片:一个进程执行它的控制流的一部分的每一时间段

    (3)并行

    两个流并发的运行不同的处理机核或者计算机上。

    并行流并行的运行,并行的执行。

    4、私有地址空间

    进程为程序提供的假象,好像它独占的使用系统地址空间。一般而言,和这个空间中某个地址相关联的那个存储器字节是不能被其他进程读写的。

    地址空间底部:保留给用户程序的,包括通常的文本、数据、堆、和栈段。

    地址空间顶部:保留给内核的,应用程序执行系统调用时使用的代码、数据、和栈。

    5、用户模式和内核模式

    简单的说,用户模式和内核模式的区别就在于用户的权限上,权限指的是对系统资源使用的权限。

    具体的区别是有无模式位,有的话就是内核模式,可以执行指令集中的所有指令,访问系统中任何存储器位置;没有就是用户模式。

    进程从用户模式变为内核模式的唯一方法是通过异常——中断,故障,或者陷入系统调用。

    Linux的聪明机制——/proc文件系统,将许多内核数据结构的内容输出为一个用户程序可以读的文本文件的层次结构。

    6、上下文切换

    操作系统内核使用上下文切换这种较高层形式的异常控制流来实现多任务。上下文切换机制建立在较底层异常机制之上。

    (1)上下文:内核重新启动一个被抢占的进程所需的状态。
    由一些对象的值组成:

    • 通用目的寄存器
    • 浮点寄存器
    • 程序计数器
    • 用户栈
    • 状态寄存器
    • 内核栈
    • 内核数据结构:页表、进程表、文件表

    (2)调度和调度器

    在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被强占的的进程。这种决定叫做调度。是由内核中称为调度器的代码处理的。

    (3)上下文切换机制

    1.保存当前进程的上下文

    2.恢复某个先前被抢占的进程被保存的上下文

    3.将控制传递给这个新恢复的进程。

    (4)可能发生上下文切换的原因:

    • 内核代表用户执行系统调用时
    • 中断

    第三节 系统调用错误处理

    简单总结就是,系统会使用错误处理包装函数,系统级函数是小写,他们的包装函数名大写,包装函数调用基本函数,有任何问题就终止,如果没有问题和基本函数是一样的。

    详见附录A

    第四节 进程控制

    一、获取进程ID

    每个进程都有一个唯一的正数进程ID(PID)。

    #include <sys/types.h>
    #include <unistd.h>
    
    pid_t getpid(void);	返回调用进程的PID
    pid_t getppid(void);	返回父进程的PID(创建调用进程的进程)
    

    二、创建和终止进程

    1.进程总是处于下面三种状态之一

    • 运行

    • 停止:被挂起且不会被调度

    • 终止:永远停止。原因:

        1.收到信号,默认行为为终止进程
        2.从主程序返回
        3.调用exit函数
      

    2.创建进程

    父进程通过调用fork函数来创建一个新的运行子进程。fork函数定义如下:

    #include <sys/types.h>
    #include <unistd.h>
    
    pid_t fork(void);
    

    fork函数只被调用一次,但是会返回两次:父进程返回子进程的PID,子进程返回0.如果失败返回-1.

    注意:

    • 调用一次,返回两次

    • 并发执行,内核能够以任何方式交替执行它们的逻辑控制流中的指令

    • 相同和不同:

        相同:用户栈、本地变量值、堆、全局变量值、代码
        不同:私有地址空间
      
    • 共享文件:子进程继承了父进程所有的打开文件。

    • 调用fork函数n次,产生2的n次方个进程。

    3.终止进程

    用exit函数。

    #include <stdlib.h>
    
    void exit(int status);
    

    exit函数以status退出状态来终止进程。

    三、回收子进程

    进程终止后还要被父进程回收,否则处于僵死状态。

    如果父进程没有来得及回收,内核会安排init进程来回收他们。init进程的PID为1.

    一个进程可以通过调用waitpid函数来等待它的子进程终止或停止。waitpid函数的定义如下:

    #include <sys/types.h>
    #include <sys/wait.h>
    
    pid_t waitpid(pid_t pid, int *status, int options);
    

    成功返回子进程PID,如果WNOHANG,返回0,其他错误返回-1.

    ①判断等待集合的成员——pid

    • pid>0:等待集合是一个单独子进程,进程ID等于pid
    • pid=-1:等待集合是由父进程所有的子进程组成
    • 其他。

    ②修改默认行为——options

    设置为常量WNOHANG和WUNTRACED的各种组合:

    ③检查已回收子进程的退出状态——status

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

    • WIFEXITED:如果子进程通过调用exit或一个返回正常终止,就返回真
    • WEXITSTATUS:返回一个正常终止的子进程的退出状态。只有在WIFEXITED返回为真时,才会定义这个状态
    • WIFSIGNALED:如果子进程是因为一个未被捕获的信号终止的,那么返回真
    • WTERMSIG:返回导致子进程终止的信号的编号。只有在WIFSIGNALED返回为真时才定义这个状态
    • WIFSTOPPED:如果引起返回的子进程当前是被停止的,那么返回真
    • WSTOPSIG:返回引起子进程停止的信号的数量。只有在WIFSTOPPED返回为真时才定义这个状态

    ④错误条件

    如果调用进程没有子进程,那么waitpid返回-1,并且设置errno为ECHILD。

    如果waitpid被一个信号中断,那么他返回-1,并且设置errno为EINTR。

    ⑤wait函数

    wait函数是waitpid函数的简单版本,wait(&status)等价于waitpid(-1,&status,0).

    #include <sys/types.h>
    #include <sys/wait.h>
    
    pid_t wait(int *status);
    

    成功返回子进程pid,出错返回-1

    四、让进程休眠

    1.sleep函数

    sleep函数使一个进程挂起一段指定的时间。定义如下:

    #include <unistd.h>
    
    unsigned int sleep(unsigned int secs);
    

    返回值是剩下还要休眠的秒数,如果到了返回0.

    2.pause函数

    #include <unistd.h>
    
    int pause(void);
    

    让调用函数休眠,直到该进程收到一个信号。

    五、加载并运行程序——execve函数

    #include <unistd.h>
    
    int execve(const char *filename, const char *argv[], const char *envp[]);
    
    成功不返回,失败返回-1.
    

    注意:execve函数调用一次,从不返回。

    • filename:可执行目标文件
    • argv:参数列表
    • envp:环境列表

    新程序开始时:

    getnev函数

    #include <stdlib.h>
    
    char *getenv(const char *name);
    
    若存在则为指向name的指针,无匹配是null
    

    在环境数组中搜寻字符串"name=value",如果找到了就返回一个指向value的指针,否则返回null。

    setenv和unsetenv函数

    #include <stdlib.h>
    
    int setenv(const char *name, const char *newvalue, int overwrite);
    
    若成功返回0,错误返回-1
    
    void unsetenv(const char *name);
    
    无返回值
    

    如果环境数组包含"name=oldvalue"的字符串,unsetenv会删除它,setenv会用newvalue代替oldvalue,只有在overwrite非零时成立。

    如果name不存在,setenv会将"name=newvalue"写进数组。

    fork函数和execve函数的区别

    • fork函数是创建新的子进程,是父进程的复制体,在新的子进程中运行相同的程序,父进程和子进程有相同的文件表,但是不同的PID

    • execve函数在当前进程的上下文中加载并运行一个新的程序,会覆盖当前进程的地址空间,但是没有创建一个新进程,有相同的PID,继承文件描述符。

    第五节 信号

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

    一、信号术语

    传递一个信号到目的进程的两个步骤:发送信号和接收信号。

    (内核)发送信号的原因:

    1.内核检测到一个系统事件
    2.一个进程调用了kill函数,显式的要求内核发送一个信号给目的进程。
    

    注意:一个进程可以发送信号给它自己。

    接收信号:

    1.忽略
    2.终止
    3.执行信号处理程序,捕获信号
    

    待处理信号:

    • 只发出没有被接收的信号
    • 任何时刻,一种类型至多只会有一个待处理信号,多的会被直接丢弃
    • 一个进程可以选择性的阻塞接受某种信号,被阻塞仍可以被发送,但是不会被接收,直到解除阻塞。
    • 一个待处理信号最多只能被接收一次。
    • pending:待处理信号集合
    • blocked:被阻塞信号集合。

    二、发送信号——基于进程组

    1.进程组

    • 每个进程都只属于一个进程组。
    • 进程组ID:正整数
    • 一个子进程和他的父进程属于同一进程组。
    • 查看进程组id:getpgrp
    • 修改进程组:setpgid

    2.用/bin/kill程序发送信号

    /bin/kill程序可以向另外的进程发送任意的信号,格式是:

    Unix > /bin/kill -n m
    n是信号,m是进程或进程组
    

    当n>0时,发送信号n到进程m

    当n<0时,使信号|n|发送到进程组m中的所有进程。

    3.从键盘发送信号

    4.用kill函数发送信号

    进程通过调用kill函数发送信号给其他进程。

    5.用alarm函数发送信号

    进程可以通过调用alarm函数向它自己发送SIGALRM信号。

    #include <unistd.h>
    
    unsigned int alarm(unsigned int secs);
    
    返回前一次闹钟剩余的秒数,若没有返回0.
    

    第六节 非本地跳转

    c语言中,用户级的异常控制流形式,通过setjmp和longjmp函数提供。

    setjump函数在env缓冲区中保存当前调用环境,以供后面longjmp使用,并返回0.

    调用环境:程序计数器,栈指针,通用目的寄存器

    setjmp函数只被调用一次,但返回多次;

    longjmp函数被调用一次,但从不返回。

    第七节 操作进程的工具

    • STRACE:打印一个正在运行的程序和他的子程序调用的每个系统调用的痕迹
    • PS:列出当前系统中的进程,包括僵死进程
    • TOP:打印出关于当前进程资源使用的信息
    • PMAP:显示进程的存储器映射

    代码实践

    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;
    }
    

    1、可以看到这个代码中用了execvp函数。

    表头文件:#include<unistd.h>

    定义函数: int execvp(const char *file ,char * const argv []);

    2、execvp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名,找到后便执行该文件,然后将第二个参数argv传给该欲执行的文件。

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

    exec2

    它与exec1的区别就在于exevp函数的第一个参数,exec1传的是ls,exec2直接用的arglist[0],不过由定义可得这两个等价,所以运行结果是相同的。

    exec3

    代码如下:

    #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
    ");
    }
    

    1、这个代码里使用了execlp函数,用法如下:

    头文件:#include<unistd.h>
    定义函数:int execlp(const char * file,const char * arg,....);

    2、函数说明:

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

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

    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.

    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输出。

    forkdemo3

    代码如下:

    #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());
    	
    		exit(0);
    	}
    	else{
    		printf("I am the parent. my child is %d
    ", fork_rv);
    		exit(0);
    	}
    
    	return 0;
    }
    

    代码分析:fork产生子进程,父进程返回子进程pid,不为0,所以输出父进程的那句话,子进程返回0,所以会输出子进程那句话。

    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与之后一句。

    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;
    }
    

    代码分析:这个的主要区别是在,父进程打印是先打印两句,然后休眠一秒,然后打印一句,子进程先打印一句,然后休眠一秒,然后打印两句。并且这两个线程是并发的,所以可以看到在一个线程休眠的那一秒,另一个线程在执行,并且线程之间相互独立互不干扰。

    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;
    }
    

    代码分析:这个代码就相当于你输入要执行的指令,回车表示输入结束,然后输入的每个参数对应到函数中,再调用对应的指令。

    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;
    }
    

    代码分析:比起1来,多了循环判断,不退出的话就会一直要你输入指令,并且对于子程序存在的状态条件。

    testbuf1:

    #include <stdio.h>
    #include <stdlib.h>
    int main()
    {
    	printf("hello");
    	fflush(stdout);
    	while(1);
    }
    

    效果是先输出hello,然后换行。之后不退出。

    【图9

    testbuf2

    #include <stdio.h>
    int main()
    {
    	printf("hello
    ");
    	while(1);
    }
    

    代码分析:效果同上。可知:fflush(stdout)的效果和换行符 是一样的。

    testbuf3

    #include <stdio.h>
    
    int main()
    {
    	fprintf(stdout, "1234", 5);
    	fprintf(stderr, "abcd", 4);
    }
    

    代码分析:将内容格式化输出到标准错误、输出流中。

    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。

    testpp

    #include <stdio.h>
    #include <stdlib.h>
    int main()
    {
    	char **pp;
    	pp[0] = malloc(20);
    
    	return 0;
    }
    

    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发送这两个命令,分别执行。如下图,输入ls和dir两个指令后,可以看到分别执行了。
    【图13

    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。

    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);
    }
    

    代码分析:这个比起1来就是多了一个子进程的状态区分,把状态拆分成三块,exit,sig和core。

    本周代码托管截图

    代码链接

    学习进度条

    代码行数(新增/累积) 博客量(新增/累积) 学习时间(新增/累积) 重要成长
    目标 5000行 30篇 400小时
    第一周 150/150 1/2 20/20
    第二周 200/350 1/2 24/44
    第三周 150/500 1/3 20/64
    第五周 300/800 1/4 15/79
    第六周 500/1300 1/5 20/99
    第七周 200/1500 1/6 21/120
    第九周 210/1710 1/9 10/130
    第十周 530/2240 2/11 20/150
    第十一周 900/3140 1/12 30/180

    参考资料

  • 相关阅读:
    转:MVC分页
    转:更新Android SDK之后Eclipse提示ADT版本过低的一个简易解决办法
    DataGridView 添加鼠标右键选择行
    WinForm 中使用ScintillaNet
    C#获取当前程序运行路径的方法集合
    Winform DataGridView鼠标右键选择列
    EF 数据查询(更改默认排序)
    使用lambda表达式进行对象结合的筛选操作
    使用SSIS生成数据导出为Excel文件的模板
    Sql server 数据库中计算每天的开始结束时间
  • 原文地址:https://www.cnblogs.com/GZSdeboke/p/6107872.html
Copyright © 2011-2022 走看看