Linux编程基础 3.2:exec函数族

本文详细介绍了如何使用fork创建子进程,并探讨了exec函数族在Linux中替换进程代码和环境的应用。通过实例演示了如何让子进程执行ls命令,以及execl和execvp的区别,同时涵盖了孤儿进程和僵尸进程的概念。
摘要由CSDN通过智能技术生成

2 exec函数族

使用fork()函数创建的子进程,其中包含的程序代码完全相同,只能根据fork()函数的返回值,执行不同的代码分支。
由exec函数族中的函数,则可以根据指定的文件名或路径,找到可执行文件。

在这里插入图片描述

  • fork:子进程复制父进程的堆栈段和数据段,子进程一旦开始运行,它继承了父进程的一切数据,但实际上数据却已经分开,相互之间不再影响;
  • exec:一个进程调用exec类函数,它本身就"死亡"了,系统把代码段替换成新的程序代码,废弃原有数据段和堆栈段,并为新程序分配新数据段与堆栈段。

exec函数族中包含6个函数,分别为:

#include <unistd.h>
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 execvp(const char *file, char * const argv[]);
int execve(const char *path, char * const argv[], char * const envp[]);

参数说明
(1)当参数是path,传入的为路径名;当参数是file,传入的可执行文件名;
(2)可以将exec函数族分为execl和execv两类:

  • execl类:函数将以列举的形式传入参数,由于参数列表的长度不定,所以要用哨兵NULL表示列举结束;
  • execv类:函数将以参数向量表传递参数,char * argv[]的形式传递文件执行时使用的参数,数组中最后一个参数为NULL;

(3)如果没有参数char * const envp[],则采用默认环境变量;如果有,则用传入的参数替换默认环境变量;

  • 在 Linux 系统中,环境变量是用来定义系统运行环境的一些参数,比如每个用户不同的家目录(HOME)、邮件存放位置(MAIL)等
    [root@localhost ~]# env
    ORBIT_SOCKETDIR=/tmp/orbit-root
    HOSTNAME=livecd.centos
    GIO_LAUNCHED_DESKTOP_FILE_PID=2065
    TERM=xterm
    SHELL=/bin/bash

【案例 4】在程序中创建一个子进程,之后使父进程打印自己的pid信息,使子进程通过exec函数簇获取系统命令文件,执行ls命令。

test_exec.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
    pid_t tempPid;
    tempPid=fork();
    if(tempPid == -1){   
        perror("fork error");
        exit(1);
    } else if(tempPid > 0) {   
        printf("parent process:pid=%d\n", getpid());
    } else {   
        printf("child process:pid=%d\n", getpid());
        //execl("/bin/ls","-a","-l","test_exec.c",NULL);	//①
        //execlp("ls","-a","-l","test_exec.c",NULL);	//②
        char *arg[]={"-a","-l","test_exec.c", NULL};	//③
        execvp("ls", arg);
        perror("error exec\n");
        printf("child process:pid=%d\n", getpid());
    } //of if  
    return 0;
} //of main

【问】:如果execvp(“ls”, arg);执行成功,不再执行19,20对应的语句,请根据execvp的函数特点分析原因。
【答】:一个进程调用exec类函数,它本身就"死亡"了,系统把代码段替换成新的程序代码,废弃原有数据段和堆栈段,并为新程序分配新数据段与堆栈段。

【案例5】在执行文件execl.c过程中调用execl,从而执行echoarg.c。

文件echoarg.c
#include <stdio.h>

int main(int argc,char *argv[]) {
    int i = 0;
    for(i = 0; i < argc; i++) {
        printf("argv[%d]: %s\n",i,argv[i]);
    }//of if
    return 0;
}//of main

编译echoarg.c并运行,结果如下:
在这里插入图片描述

文件execl.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void){
    printf("before execl\n");
    //if(execl("./echoarg", "file1","file2",NULL) == -1){
    if(execl("./echoarg","./echoarg", "file1","file2",NULL) == -1){
        printf("execl failed!\n");
        perror("why");
    }//of if
    printf("after execl\n");
    return 0;
}//of main

编译execl.c并运行,结果如下:
在这里插入图片描述
程序分析:

  • 若调用execl失败,返回-1,然后执行 printf(“after execl\n”);(打印"after execl");
  • 若成功则不执行 printf(“after execl\n”);(不打印"after execl")。

【实例6】获取日期和时间
Linux指令date 可以用来显示或设定系统的日期与时间:
在这里插入图片描述
调用execl函数来获取日期和时间:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void){
    printf("before execl\n");
    if(execl("/bin/date","date",NULL,NULL) == -1){
        printf("execl failed!\n");
        perror("why");
    }//of if
    printf("after execl\n");
    return 0;
}//of main

编译运行结果如下:
在这里插入图片描述

3 进程退出

#include <stdlib.h>
void exit(int status);

参数说明
(1)status:表示进程的退出状态,0表示正常退出,非0表示异常退出,一般用-1或1表示;
(2)为了可读性,标准C定义了两个宏:EXIT_SUCCESS和EXIT_FAILURE

Linux系统中有一个与exit()函数非常相似的函数:_exit()

#include <unistd.h>
void _exit(int status);

区别:

  • _exit:系统会无条件停止操作,终止进程并清除进程所用内存空间及进程在内核中的各种数据结构;
  • exit:对_exit进行了包装,在调用_exit()之前先检查文件的打开情况,将缓冲区中的内容写回文件。相对来说exit比_exit更为安全

【案例7】

#include <stdio.h>
#include <stdlib.h>

int main(int argc, const char *argv[]){
    /* 标准输出流stdout是行缓冲,遇到“\n”换行符时才实际写入终端。 */
    printf("Using exit...\n");
    /* 下一个输出不输出换行符 */
    printf("This is the content in buffer.");
    exit(0); /* 调用exit函数测试 */
    printf("After the exit...\n");
    return 0;
}//of main

4 特殊进程

  • 孤儿进程:父进程负责回收子进程,如果父进程在子进程退出之前退出,子进程就会变成孤儿进程,此时init进程将代替父进程完成子进程的回收工作;
  • 僵尸进程:调用exit函数后,该进程不会马上消失,而是留下一个称为僵尸进程的数据结构。它几乎放弃进程退出前占用的所有内存,既没有可执行代码也不能被调度,只是在进程列表中保留一个位置,记载进程的退出状态等信息供父进程回收。若父进程没有回收子进程的代码,子进程将会一直处于僵尸态。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HenrySmale

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值