嵌入式Linux学习---进程(1)

什么是一个进程?当用户敲入命令执行一个程序的时候,对系统而言,它将启动一个进程。但和程序不一样的是,在这个进程中,系统可能须要再启动一个或多个进程来完成独立的多个任务。多进程编程的主要内容包括进程控制和进程间通讯。shell

 

1       Linux下进程的结构

Linux下一个进程在内存里有三部分的数据,就是"代码段"、"堆栈段"和"数据段"。这三个部分也是构成一个完整的执行序列的必要的部分。编程

 

"代码段",顾名思义,就是存放了程序代码的数据,假如机器中有数个进程运行相同的一个程序,那么它们就可使用相同的代码段。"堆栈段"存放的就是子程序的返回地址、子程序的参数以及程序的局部变量。而数据段则存放程序的全局变量,常数以及动态数据分配的数据空间(好比用malloc之类的函数取得的空间)。系统若是同时运行数个相同的程序,它们之间就不能使用同一个堆栈段和数据段。小程序

 

2       Linux下的进程控制

Linux环境下,有两个基本的操做用于建立和修改进程:函数fork()用来建立一个新的进程,该进程几乎是当前进程的一个彻底拷贝;函数族exec()用来启动另外的进程以取代当前运行的进程。框架

 

2.1    fork()

fork在英文中是"分叉"的意思。为何取这个名字呢?由于一个进程在运行中,若是使用了fork,就产生了另外一个进程,因而进程就"分叉"了,因此这个名字取得很形象。下面就看看如何具体使用fork,这段程序演示了使用fork的基本框架:函数

void main(){操作系统

        int i;设计

        if ( fork() == 0 ) {继承

                /* 子进程程序 */进程

                for ( i = 1; i <1000; i ++ ) printf("This is child process/n");ip

        }

        else {

                /* 父进程程序*/

                for ( i = 1; i <1000; i ++ ) printf("This is process process/n");

        }

}

 

程序运行后,你就能看到屏幕上交替出现子进程与父进程各打印出的一千条信息了。若是程序还在运行中,你用ps命令就能看到系统中有两个它在运行了。

 

那么调用这个fork函数时发生了什么呢?fork函数启动一个新的进程,这个进程几乎是当前进程的一个拷贝:子进程和父进程使用相同的代码段;子进程复制父进程的堆栈段和数据段。这样,父进程的全部数据均可以留给子进程,可是,子进程一旦开始运行,虽然它继承了父进程的一切数据,但实际上数据却已经分开,相互之间再也不有影响了,也就是说,它们之间再也不共享任何数据了。它们再要交互信息时,只有经过进程间通讯来实现,这将是咱们下面的内容。

 

既然它们如此相象,系统如何来区分它们呢?这是由函数的返回值来决定的。对于父进程,fork函数返回了子程序的进程号,而对于子程序,fork函数则返回零。在操做系统中,咱们用ps函数就能够看到不一样的进程号,对父进程而言,它的进程号是由比它更低层的系统调用赋予的,而对于子进程而言,它的进程号便是fork函数对父进程的返回值。在程序设计中,父进程和子进程都要调用函数fork()下面的代码,而咱们就是利用fork()函数对父子进程的不一样返回值用if……else……语句来实现让父子进程完成不一样的功能。咱们看到,上面例子执行时两条信息是交互无规则的打印出来的,这是父子进程独立执行的结果,虽然咱们的代码彷佛和串行的代码没有什么区别。

 

读者也许会问,若是一个大程序在运行中,它的数据段和堆栈都很大,一次fork就要复制一次,那么fork的系统开销不是很大吗?不管是数据段仍是堆栈段都是由许多"页"构成的,fork函数复制这两个段,只是"逻辑"上的,并不是"物理"上的,也就是说,实际执行fork时,物理空间上两个进程的数据段和堆栈段都仍是共享着的,当有一个进程写了某个数据时,这时两个进程之间的数据才有了区别,系统就将有区别的"页"从物理上也分开。系统在空间上的开销就能够达到最小。

 

下面演示一个足以"搞死"Linux的小程序,其源代码很是简单:

void main()

{

  for( ; ; ) fork();

}

这个程序什么也不作,就是死循环地fork,其结果是程序不断产生进程,而这些进程又不断产生新的进程,很快,系统的进程就满了,系统就被这么多不断产生的进程"撑死了"。固然只要系统管理员预先给每一个用户设置可运行的最大进程数,这个恶意的程序就完成不了企图了。

 

2.2    exec( )函数族

下面咱们来看看一个进程如何来启动另外一个程序的执行。在Linux中要使用exec函数族。系统调用execve()对当前进程进行替换,替换者为一个指定的程序,其参数包括文件名(filename)、参数列表(argv)以及环境变量(envp)。exec函数族固然不止一个,但它们大体相同,在Linux中,它们分别是:execl,execlp,execle,execv,execve和execvp,下面我只以execlp为例。

 

一个进程一旦调用exec类函数,它自己就"死亡"了,系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,惟一留下的,就是进程号,也就是说,对系统而言,仍是同一个进程,不过已是另外一个程序了。

 

那么若是个人程序想启动另外一程序的执行但本身仍想继续运行的话,怎么办呢?那就是结合fork与exec的使用。下面一段代码显示如何启动运行其它程序:

 

char command[256];

void main()

{

        int rtn; /*子进程的返回数值*/

        while(1) {

                /* 从终端读取要执行的命令 */

                printf( ">" );

                fgets( command, 256, stdin );

                command[strlen(command)-1] = 0;

                if ( fork() == 0 ) {

                       /* 子进程执行此命令 */

                       execlp( command, command );

                       /* 若是exec函数返回,代表没有正常执行命令,打印错误信息*/

                       perror( command );

                       exit( errorno );

                }

                else {

                       /* 父进程, 等待子进程结束,并打印子进程的返回值 */

                       wait ( &rtn );

                       printf( " child process return %d/n",. rtn );

                }

        }

}

 

此程序从终端读入命令并执行之,执行完成后,父进程继续等待从终端读入命令。

 

在这一节里,咱们还要讲讲system()和popen()函数。system()函数先调用fork(),而后再调用exec()来执行用户的登陆shell,经过它来查找可执行文件的命令并分析参数,最后它么使用wait()函数族之一来等待子进程的结束。函数popen()和函数system()类似,不一样的是它调用pipe()函数建立一个管道,经过它来完成程序的标准输入和标准输出。

相关文章
相关标签/搜索