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

教材学习内容总结

8.3 系统调用错误处理

错误处理包装函数:包装函数调用基本函数,检查错误,如果有任何问题就终止。

8.4 进程控制

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)终止:进程用永远停止了。
终止原因:
(1)收到一个信号,默认行为是终止进程
(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)发送信号:内核通过更新目的进程上下文中的某个状态,发送(递送)一个信号给目的进程。
(2)接收信号:信号处理程序捕获信号的基本思想。
发送信号的两个原因:
(1)内核监测到一个系统事件,比如被零除错误或者子进程终止。
(2)一个进程调用了kill函数,显式地要求内核发送一个信号给目的进程。一个进程可以发送信号给它自己。
2、待处理信号:一个只发出而没有被接收的信号
一个进程可以有选择性地阻塞接收某种信号。
待处理信号不会被接收,直到进程取消对这种信号的阻塞。
3、一个待处理信号最多只能被接受一次,pending位向量:维护着待处理信号集合,blocked向量:维护着被阻塞的信号集合。

8.6非本地跳转

通过setjmp和longjmp函数来提供
setjmp函数只被调用一次,但返回多次:一次是当第一次调用setjmp,而调用环境保存在缓冲区env中时,一次是为每个相应的longjmp调用
longjmp只调用一次,但从不返回
非本地跳转的一个重要应用就是允许从一个深层嵌套的函数调用中立即返回,通常是由检测到某个错误情况引起的
非本地跳转的另一个重要应用是使一个信号处理程序分支到一个特殊的代码位置,而不是返回到达中断了的指令位置

8.7 操作进程的工具

Linux系统提供监控和操作进程的工具:打印一个正在运行的程序和它的子进程调用的每个系统调用的轨迹(STRACE)、列出当前系统中包括僵死进程的进程(PS)、打印出关于当前进程资源使用的信息(TOP)、显示进程的存储器映射(PMAP)、虚拟文件系统(/proc)

代码学习内容总结

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\n");
execvp( "ls" , arglist );
printf("* * * ls is done. bye");

return 0;
}
···

exevp函数调用成功没有返回,所以没有打印出“* * * ls is done. bye”这句话。

exec2

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\n");

execlp("ls", "ls", "-l", NULL);
printf("* * * ls is done. bye\n");
}

这个代码指定了环境变量,然后依然执行了ls -l指令,成功后没有返回,所以最后一句话不会输出。运行结果同exec1.

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\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.

forkdemo2

代码:

#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

代码

 #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());

exit(0);
}
else{
printf("I am the parent. my child is %d\n", 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\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一句,休眠十秒;子进程返回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\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

代码

#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;
charargbuf[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;
}

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

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] = '\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;
charargbuf[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;
}

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

testbuf1

代码:

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

输出hello,换行不退出

testbuf2

代码:

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

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 \n", getpid());
printf("my parent's pid: %d \n", getppid());
return 0;
}

输出当前进程pid和当前进程的父进程的pid。

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

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发送一条指令。

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

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

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

本周代码托管截图

posted @ 2016-11-27 22:43  20145310刘宇飞  阅读(187)  评论(0编辑  收藏  举报