20145204 张亚军《信息安全系统设计基础》第12周学习总结

代码分析

关于fork()函数的疑问

1、调用一次,返回两次。
fork函数被父进程调用,当前的父进程是哪个进程?返回两次,什么叫返回两次?同样的代码运行两次吗?
2、相同的但是独立的地址空间。
在返回两次,子进程和父进程都是再次从main()函数进入重新执行吗?
3、并发执行
并发执行是cpu随意指定,想先运行哪个就是哪个?

p493页代码分析
#include "csapp.h"

int main() 
{
    pid_t pid;
    int x = 1;

    pid = Fork(); //返回两个进程,pid=2780;
    printf("pid %d\n",pid);
    if (pid == 0) {  //子进程返回的pid为0;父进程返回子进程的pid;
	printf("child : x=%d\n", ++x); 
	exit(0);
    }
    printf("parent: x=%d\n", --x); 
    exit(0);
}

结果显示:

  • 调用一次返回两次:
    在语句pid = Fork();之前,只有一个进程在执行这段代码,但在这条语句之后,就变成两个进程在执行了,这两个进程的几乎完全相同,将要执行的下一条语句都是
printf("pid %d\n",pid);
if (pid == 0)

如果fork返回的pid>0表示当前运行进程为父进程,若pid==0,则表示当前进程为子进程;

  • 有独立的地址空间:父子进程都是在x=1基础上进行运算,父进程执行后x=0;子进程x=2。
forkdemo2.c
#include <stdio.h>
#include <unistd.h>

int main()
{
        int pid1,pid2;
        printf("before:my pid is %d\n", getpid() );
        pid_t pid1=fork();
        pid_t pid2=fork();
        printf("我的父亲pid是:%d,我的pid是: %d,我的儿子pid是:\n", getppid(),getpid(),pid1);

        return 0;
}

结果显示:

  • 分析代码,会返回4个进程,但是最后printf后的三个子进程的父进程的pid均是1351,后经过查阅资料,在这里涉及到进程的死亡。在第一次父进程printf结束后,父进程死亡,但是子进程不能没有父进程,所以将他们的父进程改到1351。ps -A查看1351是upstart进程。

exec函数

在分析代码前需要知道:

fork函数是用于创建一个子进程,该子进程几乎是父进程的副本,而有时我们希望子进程去执行另外的程序,exec函数族就提供了一个在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新程序的内容替换了。

在Linux中并没有exec函数,而是有6个以exec开头的函数族

  • 查找方式:以p结尾的两个函数可以只给出文件名,系统就会自动从环境变量“$PATH”所指出的路径中进行查找。

  • 参数传递方式:参数传递方式是以函数名的第5位字母来区分的,字母为“l”(list)的表示逐个列举的方式,字母为“v”(vertor)的表示将所有参数整体构造成指针数组传递,然后将该数组的首地址当做参数传给它,数组中的最后一个指针要求是NULL。

  • 环境变量:exec函数族使用了系统默认的环境变量,也可以传入指定的环境变量。这里以“e”(environment)结尾的两个函数execle、execve就可以在envp[]中指定当前进程所使用的环境变量替换掉该进程继承的所以环境变量

execvp函数
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 );//只给出文件名ls,参数整体构造成指针数组传递
	printf("* * * ls is done. bye");//结束后不返回到主进程中,不打印该句。

	return 0;
}

env

getenv()函数(取得环境变量内容)
  • 定义函数 char * getenv(const char *name);
  • 函数说明 getenv()用来取得参数name环境变量的内容。参数name为环境变量的名称,如果该变量存在则会返回指向该内容的指针。环境变量的格式为name=value。
  • 返回值:执行成功则返回指向该内容的指针,找不到符合的环境变量名称则返回NULL。
setenv()函数(改变或增加环境变量)
  • 定义函数 int setenv(const char *name,const char * value,int overwrite);name为环境变量名称字符串。 参数 value则为变量内容。
  • 函数说明 setenv()用来改变或增加环境变量的内容。
  • 参数 overwrite用来决定是否要改变已存在的环境变量。如果overwrite不为0,则改变环境变量原有内容,原有内容会被改为参数value所指的变量内容。如果overwrite为0,且该环境变量已有内容,则参数value会被忽略。
  • 返回值 执行成功则返回0,有错误发生时返回-1。  错误代码 ENOMEM 内存不足,无法配置新的环境变量空间
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
        printf("PATH=%s\n", getenv("PATH"));
        setenv("PATH", "hello", 0);//overwrite=0,不改变path的值
        printf("PATH=%s\n", getenv("PATH"));
#if 1
        printf("PATH=%s\n", getenv("PATH"));
        setenv("PATH", "hellohello", 1);//path=hellohello
        printf("PATH=%s\n", getenv("PATH"));


        printf("MY_VER=%s\n", getenv("MY_VER"));//新的一个环境变量名称。MY_VER=null;
        setenv("MY_VER", "1.1", 0);//虽然overwrite=0,但是由于该变量无内容,所以最后MY_VER=1.1;
        printf("MY_VER=%s\n", getenv("MY_VER"));
#endif
        return 0;
}

pipe函数?

关于pipe函数,看上去很简单,但实际上我个人感觉是问题最多的一个。pipe相当于一个管道,从一端读文件,一端用来写文件,如果和fork函数配套使用的话,不是应该父进程(或者子进程)只能读,剩下一个只能写吗,为什么程序中父进程和子进程同时拥有读和写的功能close( filedes[0] ); close( filedes[1] );。查阅众多资料后,终于明白了。上代码分析:

#define	oops(m,x)	{ perror(m); exit(x); }

int main(int ac, char **av)
{
	int	thepipe[2],			
		newfd,				
		pid;				

	if ( ac != 3 )//如果输入的命令的字符!=3,提示用法为:pip  cmd1 cmd2.
       {
		fprintf(stderr, "usage: pipe cmd1 cmd2\n");
		exit(1);//提出程序
	}
	if ( pipe( thepipe ) == -1 )	//建立管道	
		oops("Cannot get a pipe", 1);

	if ( (pid = fork()) == -1 )//fork建立有血缘关系的两个进程。			
		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);
}

  • 管道通信是单向的,并且遵守先进先出的原则,即先写入的数据先读出。
  • 管道是一个无结构,无固定大小的字节流。
  • 管道把一个进程的标准输出和另一个进程的标准输入连接在一起。数据读出后就意味着从管道中移走了,消失了。其它的进程都不能再读到这些数据。就像我们平常见到的管子水流走了就没有了。

学习进度条

代码行数(新增/累积) 博客量(新增/累积) 学习时间(新增/累积) 重要成长
目标 3500行 30篇 400小时
第一周 40/40 1/1 20/20
第二周 30/70 1/2 30/50
第三周 21/91 1/3 28/78
第五周 131/222 1/4 20/98
第六周 32/254 1/5 21/119
第七周 200/454 1/5 21/119
第八周 0/454 2/7 10/129
第九周 84/538 2/9 15/144
第10周 441/979 2/11 10/154
第11周 460/1439 2/13 20/174
第11周 300/1739 2/15 20/194

代码托管

代码链接

参考资料

posted @ 2016-12-04 22:16  20145204张亚军  阅读(120)  评论(0编辑  收藏  举报