Linux进程编程介绍(二)
摘要:本节先介绍一些关于进程的基本操作,通过本节,我们将了解如何产生子进程,进程如何改变它的执行映像,父子进程的同步等操作。由此也了解到一些并行程序的基本概念与如何编制简单的并行程序。
2. 进程的一般操作
上一节介绍了一些有关进程的基本概念,从这一节开始要结合一些例子来阐述一些有关进程的系统调用。本节先介绍一些关于进程的基本操作,通过本节,我们将了解如何产生子进程,进程如何改变它的执行映像,父子进程的同步等操作。由此也了解到一些并行程序的基本概念与如何编制简单的并行程序。
2.1 fork 系统调用
系统调用fork是用来创建一个子进程。创建的过程前面一节已经介绍过。现在,再介绍一个系统调用vfork,这个调用的产生是因为认识到创建子进程时对父进程的所有页不进行拷贝能带来性能上的改善。该调用假定进行vfork调用后,将立即调用exec,这样就不需要拷贝父进程的所有页表。因为它不拷贝页表,所以比fork调用快。有些系统的fork也采用了其他方法来提高性能,比较典型的一种是增加“写时拷贝”。这种fork调用,产生子进程时,并不拷贝父进程的所有页面,而是置父进程所有页面的写时拷贝位,子进程共享父进程的所有页面。直到父进程或子进程写某个页面时,就会发生一个保护性错误,并拷贝该页面。这样不仅提高了内核的性能,而且改善了内存的利用。
系统调用fork和vfork的声明格式如下:
pid_t fork(void);
pid_t vfork(void);
在使用该系统调用的程序中要加入以下头文件:
#include
当调用执行成功时,该调用对父进程返回子进程的PID,对子进程返回0。调用失败时,给父进程返回-1,没有子进程创建。
下面是发生错误时,可能设置的错误代码errno:
EAGAIN:系统调用fork不能得到足够的内存来拷贝父进程页表。或用户是超 级用户但进程表满,或者用户不是超级用户但达到单个用户能执行的最大进程数。
ENOMEM:对创建新进程来说没有足够的空间,该错误是指没有足够的空间分配给必要的内核结构。
下面我们看一个fork调用的简单的例子。该例子产生一个子进程,父进程打印出自己和子进程的PID,子进程打印出自己的PID和父进程的PID。
注意:父进程打开了一个文件。父子进程都可以对该文件操作,该程序父子进程都向文件中写入了一行。
#include
#include
#include
#include
#include
#include
#include
#include
extern int errno;
int main()
{
char buf[100];
pid_t cld_pid;
int fd;
int status;
if ((fd=open("temp",O_CREAT|O_TRUNC|O_RDWR,S_IRWXU)) == -1)
{
printf("open error %d",errno);
exit(1);
}
strcpy(buf,"This is parent process write");
if ((cld_pid=fork()) == 0)
{ /* 这里是子进程执行的代码 */
strcpy(buf,"This is child process write");
printf("This is child process");
printf("My PID(child) is %d",getpid()); /*打印出本进程的ID*/
printf("My parent PID is %d",getppid()); /*打印出父进程的ID*/
write(fd,buf,strlen(buf));
close(fd);
exit(0);
}
else
{ /* 这里是父进程执行的代码 */
printf("This is parent process");
printf("My PID(parent) is %d",getpid()); /*打印出本进程的ID */
printf("My child PID is %d",cld_pid); /*打印出子进程的ID*/
write(fd,buf,strlen(buf));
close(fd);
}
wait(&status); /* 如果此处没有这一句会如何?*/
return 0;
}
下面我们看一下,程序运行的结果,假设源文件命名为fork.c:
[root@wapgw /root]# gcc -o fork fork.c
[root@wapgw /root]# ./fork
This is parent process
This is child process
My PID(child) is 5258
My parent PID is 5257
My PID(parent) is 5257
My child PID is 5258
[root@wapgw /root]#
从上面的运行结果可以看出进程的调度,父进程打印出第一行后,CPU调度子进程,打印出后续的三行,子进程结束,调度父进程执行(其中可能还有其他的进程被调度),父进程执行完,将控制返还给shell程序,最后一行是shell程序输出的提示符。
看看temp文件里有什么内容
[root@wapgw /root]# more temp
This is child process write
This is parent process write
[root@wapgw /root]#
现在我们将程序稍作修改。将wait调用注释掉,我们看看会有什么样的结果。因为调度的原因,多执行几次,你会看到如下的结果:
[root@wapgw /root]#vi fork.c //将wait调用注释掉
[root@wapgw /root]# gcc -o fork fork.c
[root@wapgw /root]# ./fork
This is parent process
This is child process
My PID(parent) is 5282
My child PID is 5283
[root@wapgw /root]# My PID(child) is 5283
My parent PID is 1
[root@wapgw /root]#
第一行是父进程的输出,第二行是子进程的输出,第三、四行是父进程的输出,这时父进程由于没有wait调用,不等待子进程而结束。下面一行中的“[root@wapgw /root]#”是父进程结束,将控制返回给shell时,shell输出的提示符。然后CPU调用子进程,输出子进程的PID是5283。注意,下面子进程输出其父进程的ID是1,因为它的父进程结束了,内核将它交给了进程1(进程init)来管理,这个过程见前面一节。这里要提一下的是,输出结果的顺序和进程调度的顺序有关,自己试验的结果与例子中的顺序很可能不同,请自行分析。
2.2 exec 系统调用
系统调用exec是用来执行一个可执行文件来代替当前进程的执行映像。需要注意的是,该调用并没有生成新的进程,而是在原有进程的基础上,替换原有进程的正文,调用前后是同一个进程,进程号PID不变。但执行的程序变了(执行的指令序列改变了)。它有六种调用的形式,随着系统的不同并不完全与以下介绍的相同。它们的声明格式如下:
int execl( const char *path, const char *arg, ...);
int execlp( const char *file, const char *arg, ...);
int execle( const char *path, const char *arg , ..., char* const envp[]);
int execv( const char *path, char *const argv[]);
int execve( const char *filename, char *const argv [], char *const envp[]);
int execvp( const char *file, char *const argv[]);
在使用这些系统调用的程序中要加入以下头文件和外部变量:
#include
extern char **environ;
下面我们先详细讲述其中的一个,然后再给出它们之间的区别。在系统调用execve中,参数path是将要执行的文件,参数argv是要传递给文件的参数,参数envp是要传递给文件的环境变量。当参数path所指的文件替换原进程的执行映像后,文件path开始执行,参数argv和envp便传递给进程。下面我们举一个简单的例子。
在讲述系统调用execve的例子之前,我们先来看看环境变量。为了使用户方便和灵活地使用Shell,LINUX引入了环境的概念。环境是一些数据,用户可以改变这些数据,增加新的数据或删除一些数据。这些数据称为环境变量。因为它们定义了用户的工作环境,同时又可以被修改。每个用户都可以有自己不同的环境变量,用户可以用env命令(不带参数)浏览环境变量,输出的格式和变量名随着Shell的不同和系统配置的不同而不同。下面这个例子打印出传递给该进程的所有参数和环境变量:
#include
#include
extern char **environ;
int main(int argc,char* argv[])
{
int i;
printf("Argument:");
for (i=0;i printf("Environment:");
for (i=0;environ[i]!=NULL;i++) printf("%s",environ[i]);
}
下面是执行时的屏幕拷贝:
[root@wapgw /root]# gcc -o example example.c
[root@wapgw /root]# ./example test
Argument:
Arg0 is ./example
Arg1 is test
Environment:
PWD=/root
REMOTEHOST=cjm
HOSTNAME=wapgw
HOME=/root
。。。。。。。。。。。。。。。。。。。。。。。
SSH_ASKPASS=/usr/libexec/ssh/gnome-ssh-askpass
PATH=/usr/local/sbin:/usr/sbin:/sbin:/usr/local/sbin:/usr/local/bin:
/sbin:/bin:/usr/sbin:/usr/bin:/usr/X11R6/b