五、执行新程序
- 当已经创建子进程后,子进程通常会调用exec函数来执行另一个程序。系统调用exec用于执行一个可执行程序来代替当前进程的执行映像。在exec调用的过程中没有产生新的子进程,当一个进程时他本身就死亡了,系统把代码替换成新程序的代码,废弃原有的数据段,堆栈段,用新的代替。唯一不变的就是进程ID,对系统而言还是同一个进程,但执行的已经是另一个程序了
-
exec函数
说起exec函数首先要了解环境变量概念,本文不做描述
Linux 下exec函数族有六种不同的调用形式
-
#include <unistd.h> int execl(const char *path, const char *arg, ...) int execv(const char *path, char *const argv[]) int execle(const char *path, const char *arg, ..., char *const envp[]) int execve(const char *path, char *const argv[], char *const envp[]) int execlp(const char *file, const char *arg, ...) int execvp(const char *file, char *const argv[])
下面介绍exec函数是如何将参数传给main函数的
- execv函数:通过路径名方式调用可执行文件作为新的进程映像。他的argv参数用来提供给main函数的argv参数。argv参数是一个以NULL结尾(最后一个元素必须是一个空指针)的字符串数组
- execve函数:其中参数path是将要执行的程序的路径名,参数argv,envp与main函数的argv,envp对应。(argv和envp的大小都是有限制的,若他们容量和超过ARG_MAX定义的值会发生错误)
- execl函数:该函数与execv函数用法类似,只是在传递argv参数时,每个命令行参数都声明为一个单独的参数,这些参数要以一个空指针作为结束
- execle函数:与execl函数类似,只是要显示指定环境变量。环境变量位于命令行参数最后一个参数(空指针)之后
- execvp函数:与execv函数类似,不同的是参数filename,该参数如果包含“/”,就相当于路径名;如果不包含“/”,函数就到PATH环境变量定义的目录中寻找可执行文件。
- execlp函数:与execl函数类似,他们的区别和execvp与execv区别一样。
正常情况下这几个函数是不会返回的,因为执行映像已经被换掉了,没有接受返回值的地方了。如果有一个错误的事件,将会返回-1.这些错误通常由文件名或参数错误引起。错误表如下:
errno | 错误描述 |
EACCES | 指向的文件不是可执行文件(没有可执行位) |
E2BIG | 新程序命令行参数与环境变量容量和超过了ARG_MAX |
ENOEXEC | 没有正确的格式,指定的文件无法执行 |
ENOMEM | 没有足够的内存执行指定的程序 |
ETXTBUSY | 指定的文件被一个或多个进程以可写的方式打开 |
EIO | 从文件系统读入文件时发生I/O错误 |
exec函数用法举例:
用来替换进程映像的程序
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char * argv[],char ** environ)
{
int i;
printf("I am aprocess image!\n");
printf("My pid = %d, parentpid = %d\n",getpid(),getppid());
printf("uid = %d,gid = %d\n",getuid(),getgid());
for(i = 0; i < argc; i++){
printf("argv[%d]:%s\n",i,argv[i]);
}
}
exec函数实例
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc,char * argv[],char ** environ)
{
pid_t pid;
int stat_val;
printf("Exec example!\n");
pid = fork();
switch(pid) {
case -1:
perror("Process Creation failed\n");
exit(1);
case 0:
printf("Child process is running\n");
printf("My pid = %d ,parentpid = %d\n",getpid(),getppid());
printf("uid = %d,gid =%d\n",getuid(),getgid());
execve("processimage",argv,environ);
printf("process never go to here!\n");
exit(0);
default:
printf("Parent process is running\n");
break;
}
wait(&stat_val);
exit(0);
}
运行结果:
Exec example!
Parent process is running
Child process is running
My pid = 5879 ,parentpid = 5878
uid = 1000,gid =1000
process never go to here!
从执行结果可以发现新程序的进程被替代,保持了原来进程的:
进程ID,父进程ID,实际用户ID,实际组ID,当前工作目录,根目录,创建文件时使用的屏蔽字,进程信号屏蔽字,未决警告,和进程相关的使用处理器的时间,控制终端,文件锁。
六、等待进程结束
-
wait与waitpid
当子进程比父进程先退出时,若父进程没有调用wait或waitpid函数,子进程就会进入僵死状态。
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid,int *statloc, int options);
- wait函数会使父进程暂停,一直到他的一个子进程结为止。返回值是终止运行的子进程的PID。参数statloc指的变量存放子进程的退出码,也就是子进程的main函数返回的值或子进程中exit函数的参数。如果statloc不是一个空指针状态信息将被写入他指向的变量。
- waitpid也用来等待子进程的结束,但他用来等特定的进程结束。参数pid指明要等待的子进程的PID。参数statloc的含义与wait函数中的statloc相同,options参数允许用户改变waitpid的行为,若将该参数赋值为WNOHANG,则使父进程不被挂起而立即返回并执行后面的代码。如果想让父进程周期性的检查某个特定的进程是否已经退出,可以按照如下方式调用waitpid,如果子进程尚未退出,他将返回0;如果已经退出,则返回child_pid。调用失败时返回-1.失败的原因包括没有该子进程,参数不合法等。
waitpid(child_pid,(int *) 0, WNOHANG);
pid的意义:
取 值 | 意 义 |
pid > 0 | 等待其进程ID等于pid的子进程退出 |
pid = 0 | 等待其组ID等于调用进程的组ID的任一子进程 |
pid < -1 | 等待其组ID等于pid绝对值的任一子进程 |
pid = -1 | 等待任一子进程 |
下面一段代码是等待进程的处理:
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
char *msg;
int k;
int exit_code;
printf("Study how to get exit code\n");
pid = fork();
switch(pid) {
case 0:
msg = "Child process is running";
k = 5;
exit_code = 37;
break;
case -1:
perror("Process creation failed\n");
exit(1);
default:
exit_code = 0;
break;
}
//父子进程都会执行以下这段代码,子进程pid值为0,父进程pid值为子进程的ID
if(pid != 0) { //父进程等待子进程结束
int stat_val;
pid_t child_pid;
child_pid = wait(&stat_val);
printf("Child procee has exited, pid = %d\n",child_pid);
if(WIFEXITED(stat_val))
printf("Child exited with code %d\n",WEXITSTATUS(stat_val));
else
printf("Child exited abnormally\n");
}
else { //子进程暂停5秒,在这个过程中可以运行命令ps aux查看父进程状态
while(k-->0){
puts(msg);
sleep(1);
}
}
exit(exit_code);
}
运行结果如下:
Study how to get exit code
Child process is running
Child process is running
Child process is running
Child process is running
Child process is running
Child procee has exited, pid = 18472
Child exited with code 37
七、进程的其他操作
-
获得进程ID
通过调用getpid函数获取当前进程ID
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
if((pid = fork()) == -1){
printf("fork error!\n");
exit(1);
}
if(pid == 0)
printf("getpid return %d\n",getpid());
exit(0);
}
运行结果:
getpid return 20051
子进程的ID号为5586
-
设置ID
-
setuid与setgid
#include <sys/types.h>
#include <unistd.h>
int setuid(uid_t uid);
int setgid(gid_t gid);
setuid设置实际用户ID和有效用户ID,setgid设置实际组ID和有效组ID,他们都遵守以下规则:
- 若进程有root权限,则函数将实际用户/组ID,有效用户/组ID设置为参数uid/gid
- 若进程无root权限,但uid/gid等于实际上用户/组ID,则只将有效用户/组ID设置为uid/gid,不改变实际用户/组ID
- 若以上两条都不满足,则函数调用失败,返回-1,并设置errno为EPERM
-
改变进程的优先级
- nice函数
#include <unistd.h>
int nice(int increment);
下面是nice使用方法:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/resource.h>
int main()
{
pid_t pid;
int stat_val = 0;
int oldpri, newpri;
printf("nice study\n");
pid = fork();
switch(pid) {
case 0:
printf("Child is running,CurPid is %d, ParentPid is %d\n", pid, getppid());
oldpri = getpriority(PRIO_PROCESS,0);
printf("Old priority = %d\n",oldpri);
newpri = nice(2);
printf("New priority = %d\n",newpri);
exit(0);
case -1:
perror("Process creation failed\n");
break;
default:
printf("Parent is running,ChildPid is %d, ParentPid is %d\n", pid, getpid());
break;
}
wait(&stat_val);
exit(0);
}
运行结果:
nice study
Parent is running,ChildPid is 23801, ParentPid is 23800
Child is running,CurPid is 0, ParentPid is 23800
Old priority = 0
New priority = 2
子进程的优先级由0变成2