程序与进程:
程序(program)是一个普通文件,是机器代码指令和数据的集合,这些指令和数据存储在磁盘上的一个可执行映像中。所谓可执行映像就是一个可执行文件的内容。使用6个exec函数中的一个由内核将程序读入内存,并使其执行。
进程(process)是一个动态的实体,它具有生命周期,系统中进程的生死随时发生。程序的执行实例被称为进程。
专用进程:
进程ID0是调度进程,常常被称为交换进程(swapper)。该进程并不执行任何磁盘上的程序,它是内核的一部分,因此也被称为系统进程。
它是由完成内核初始化工作的start_kernel()函数创建,又叫闲逛进程(Idle Process)。进程0执行的是cpu_idle()函数,该函数中只有一条hlt汇编指令,hlt指令在系统闲置时不仅能降低电力的使用还能减少热的产生。例如,进程0的PCB叫做init_task,在很多链表中起链表头的作用,当就绪队列没有其它进程时,闲逛进程0就被调度程序选中,以此达到省电的目的。
进程ID1通常是init进程,在自举过程结束时由内核调用。init进程绝不会终止,因为它创建和监控操作系统外层所有进程的活动。它是一个普通的用户进程,但是它以超级用户特权运行。
init进程是Linux在启动时创建的特殊进程,顾名思义,它是起始进程,是祖先,以后诞生的所有进程都是它的后代——或是它儿子,或是它的孙子。init进程为每个终端(TTY)创建一个新的管理进程,这些进程在终端上等待用户的登录。当用户正确登录后,系统再为每一个用户启动一个shell进程,由shell进程等待并接收用户输入的命令信息。此外,init进程还负责管理系统中的“孤儿”进程。
进程ID2是页精灵进程(pagedaemon)。此进程负责支持虚存系统的请页操作。是一个内核进程。
进程控制:建立新进程,运行程序,终止进程。
问题1:一个程序如何运行另一个程序?
答案1:exec系统调用
一、通常子进程调用一种exec函数以执行另一程序,当进程调用一种exec函数时,该进程完全由新程序代换,而新程序则从其main函数开始执行。因为调用exec函数并不创建新进程,所以前后的进程ID并未改变。exec只是用一个新程序替换了当前进程的正文、数据、堆和栈段。
实际上在Linux中,并不存在一个exec()的函数形式,exec指的是一组函数,一共有6个,分别是:
1 #include <unistd.h>
2 extern char **environ;
3 int execl(const char *path, const char *arg, ...);
4 int execlp(const char *file, const char *arg, ...);
5 int execle(const char *path, const char *arg, ..., char * const envp[]);
6 int execv(const char *path, char *const argv[]);
7 int execvp(const char *file, char *const argv[]);
8 int execve(const char *path, char *const argv[], char *const envp[]);/*execve第1个参数path是被执行应用程序的完整路径,第2个参数argv就是传给被执行应用程序的命令行参数,第3个参数envp是传给被执行应用程序的环境变量。argv数组和envp数组存放的都是指向字符串的指针,这两个数组都以一个NULL元素表示数组的结尾。*/
其中只有execve是真正意义上的系统调用,其它都是在此基础上经过包装的库函数。
exec函数族的作用就是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行了一个可执行文件。其可执行文件既可以是二进制文件,也可以是linux下任何可执行的脚本文件。
与一般情况不同,exec函数族的函数执行成功后不会返回,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容取代,只留下进程ID等一些表面上的信息仍保持原样,颇有些神似"三十六计"中的"金蝉脱壳"。看上去还是旧的躯壳,却已经注入了新的灵魂。只有调用失败了,它们才会返回一个-1,从原程序的调用点接着往下执行。
分析:
参数argc指出了运行该程序时命令行参数的个数,数组argv存放了所有的命令行参数,数组envp存放了所有的环境变量。(环境变量指的是一组值,从用户登录后就一直存在,很多应用程序需要依靠它来确定系统的一些细节,我们最常见的环境变量是PATH,它指出了应到哪里去搜索应用程序,如 /bin;HOME也是比较常见的环境变量,它指出了我们在系统中的个人目录。环境变量一般以字符串"XXX=xxx"的形式存在,XXX表示变量名,xxx表示变量的值。)
留心看一下这6个函数还可以发现,前3个函数都是以execl开头的,后3个都是以execv开头的,它们的区别在于,execv开头的函数是以"char *argv[]"这样的形式传递命令行参数,而execl开头的函数采用了我们更容易习惯的方式,把参数一个一个列出来,然后以一个NULL表示结束。这里的NULL的作用和argv数组里的NULL作用是一样的。
在全部6个函数中,只有execle和execve使用了char *envp[]传递环境变量,其它的4个函数都没有这个参数,这并不意味着它们不传递环境变量,这4个函数将把默认的环境变量不做任何修改地传给被执行的应用程序。而execle和execve会用指定的环境变量去替代默认的那些。
还有2个以p结尾的函数execlp和execvp,咋看起来,它们和execl与execv的差别很小,事实也确是如此,除execlp和execvp之外的4个函数都要求,它们的第1个参数path必须是一个完整的路径,如"/bin/ls";而execlp和execvp的第1个参数file可以简单到仅仅是一个文件名,如"ls",这两个函数可以自动到环境变量PATH制定的目录里去寻找。
注:在以后的编程中遇到exec函数族,一定要记得加错误判断语句,因为与其他系统调用比起来,exec很容易受伤,被执行文件的位置,权限等很多因素都能导致该调用的失败。最常见的错误是:a、找不到文件或路径,此时errno被设置为ENOENT;b、数组argv和envp忘记用NULL结束,此时errno被设置为EFAULT;c、没有对要执行文件的运行权限,此时errno被设置为EACCES。
exec()demo:

1 /** prompting shell version 2
2 **
3 ** Solves the `one-shot' problem of version 1
4 ** Uses execvp(), but fork()s first so that the
5 ** shell waits around to perform another command
6 ** New problem: shell catches signals. Run vi, press ^c.
7 **/
8
9 #include <stdio.h>
10 #include <signal.h>
11
12 #define MAXARGS 20 /* cmdline args */
13 #define ARGLEN 100 /* token length */
14
15 main()
16 {
17 char *arglist[MAXARGS+1]; /* an array of ptrs */
18 int numargs; /* index into array */
19 char argbuf[ARGLEN]; /* read stuff here */
20 char *makestring(); /* malloc etc */
21
22 numargs = 0;
23 while ( numargs < MAXARGS )
24 {
25 printf("Arg[%d]? ", numargs);
26 if ( fgets(argbuf, ARGLEN, stdin) && *argbuf != '
' )
27 arglist[numargs++] = makestring(argbuf);
28 else
29 {
30 if ( numargs > 0 ){ /* any args? */
31 arglist[numargs]=NULL; /* close list */
32 execute( arglist ); /* do it */
33 numargs = 0; /* and reset */
34 }
35 }
36 }
37 return 0;
38 }
39
40 execute( char *arglist[] )
41 /*
42 * use fork and execvp and wait to do it
43 */
44 {
45 int pid,exitstatus; /* of child */
46
47 pid = fork(); /* make new process */
48 switch( pid ){
49 case -1:
50 perror("fork failed");
51 exit(1);
52 case 0:
53 execvp(arglist[0], arglist); /* do it */
54 perror("execvp failed");
55 exit(1);
56 default:
57 while( wait(&exitstatus) != pid )
58 ;
59 printf("child exited with status %d,%d
",
60 exitstatus>>8, exitstatus&0377);
61 }
62 }
63 char *makestring( char *buf )
64 /*
65 * trim off newline and create storage for the string
66 */
67 {
68 char *cp, *malloc();
69
70 buf[strlen(buf)-1] = '