linux_c:进程(三)

进程退出

在Linux下系统进程退出的方法分为正常退出和异常退出两种
正常退出:

  • 在main函数中执行return
  • 调用exit函数
  • 调用_exit函数

异常退出:

  • 调用abort函数
  • 进程收到某个信号,而该信号使程序终止

补充:

  • exit和return的区别:exit是将控制权交给系统,return将控制权交给函数
  • exit和abort的区别:exit是正常终止,abort是异常终止
  • exit(int exit_code):exit中的参数为0代表正常终止,若为其他值表示异常终止
  • exit和_exit的区别:exit在stdlib.h中声明,而_exit在unistd.h中声明,exit要先执行一些清除操作,然后将控制权交给内核,而_exit会执行后立即返回给内核

僵尸进程:

  • 当父进程先于子进程退出,子进程会变成孤儿进程而被init收养,当子进程先于父进程退出,父进程还没有调用wait函数等待的话,子进程就会变成僵尸进程
  • 有人会说,子进程不是已经结束了吗,为什么会变成僵尸进程?其实,子进程结束之后(父进程没有结束),系统只是释放了大部分资源,还有一部分资源,如子进程的进程id号,以及一些退出时的状态,运行的时间等等都是没有被释放的,因为它不知道父进程什么时候会再此调用它,所以如果父进程没有等待的过程,那么子进程的这些资源就没有办法被回收,也就变成了僵尸进程
  • 僵尸进程的危害:系统之中的进程id号是有限的,如果系统的僵尸进程把进程id号占用完,那就无法在创建新的进程,占用系统资源

执行新程序

当创建了新的子进程之后,我们一般是不会让它做和父进程一样的事情的,这样创建出来的新进程也就没啥了意义,所以,我们可以使用exec函数族,来使新的进程去执行其他的任务
注意: exec调用并没有生成新进程,一个进程一旦调用了exec函数,那么它的原有的代码段会被替换为新的代码段,并废弃原有的数据段和堆栈段,并为新的程序分配新的数据段和堆栈段,唯一保留的就是进程id。

#include<unistd.h>
int execve(const char *path, char *const argv[], char * const envp[])
int execv(const char *path, char *const argv[])
int execle(const char *path, const char *arg, ...)
int execl(const char *path, const char *arg, ...)
int execvp(const char *file, char *const argv[])
int execlp(const char *file, const char *arg, ...)
//关于envp这个东西,实际上它是当前系统的环境变量,它是main函数里面的,main函数的完整形式:
//int main(int argc, char *argv[], char *envp[])
//通过打印envp,就可以得到环境变量的值

对于exec函数族各个函数的解释

无论是哪个exec函数,都是将可执行程序的路径,命令行参数和环境变量传递给可执行程序的main函数的

  • execv函数:execv函数是通过路径名方式调用可执行文件作为新的进程映像,它的argv参数用来提供给main函数的argv参数。argv参数是一个以NULL结尾的字符串数组
  • execve函数:参数path是将要执行的程序的路径名,参数argv,envp与main函数的argv,envp对应
  • execl函数:该命令和execv命令相似,只是再传递argv参数的时候,每个命令行参数都要声明为一个单数的参数,最后以NULL结束
  • execle函数:该函数和execl用法相似,只是要显式的指定环境变量,环境变量位于NULL之后
  • execvp函数:该函数和execv用法类似,只是如果该参数包含/,就相当于路径名,而如果没有/,函数就到PATH环境变量定义的目录下寻找可执行文件
    execlp函数:该函数类似execl函数,它们的区别和execvp与execv的区别一样
    exec函数族中只有execve是系统调用,其他都是库函数,他们运行时都调用execve
    注:我习惯使用execvp函数,但要记住execve函数
    这些函数一般情况下是不会返回的,因为进程的执行映像已经被替换,没有接受返回值的地方,如果有一个错误事件,返回-1,这些错误通常是由文件名或参数错误引起的,有兴趣的请自行百度
    linux下exec函数族是可以执行二进制的可执行文件的,也可以执行shell脚本程序,但shell脚本必须以下面的格式开头,第一行为:#interpretername[arg]。其中interpretername可以是shell或其他解释器,arg是传递给解释器的参数

附exec函数实用源码:

//用来替换进程映像的程序
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>

int main(int argc, char * argv[], char ** environ)
{
    int i;

    printf("I am process image!\n");
    printf("my gid: %d, my parent gid: %d\n", getpid(), getppid());
    printf("my uid: %d, my gid: %d\n", getuid(), getgid());

    for(i=0; i<argc; i++)
    {
        printf("argv[%d]: %s\n", i, argv[i]);
    }

    return 0;
}
//exec函数,这里使用execve函数
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
#include<wait.h>

int main(int argc, char *argv[],char ** environ)
{
    int pid;
    int stat_val; // 存储子进程退出时的状态

    printf("execve example!\n");

    pid = fork();

    switch(pid)
    {
        case -1:
            perror("process creation failed!\n");
            exit(1);
        break;

        case 0:
            printf("I am child process!\n");
            printf("my pid: %d, my parent: %d\n", getpid(), getppid());
            printf("my uid: %d, my gid: %d\n", getuid(), getgid());

            execve("processimage", argv, environ);

            printf("parent never go to here!\n");
            exit(0);
        break;

        default:
            printf("I am parent process!\n");
        break;
    }

    wait(&stat_val);

    return 0;
}
//子进程是永远不会执行到printf("parent never go to here!\n")的,因为调用execve函数之后,子进程已经被新的执行映像所代替

注意:

执行新程序后除了保持原来的进程id,父进程id,实际用户id和实际组id外,还保持了许多原有的特性:

  • 当前工作目录
  • 根目录
  • 创建文件时所用的屏蔽字
  • 进程信号屏蔽字
  • 未决警告
  • 和进程相关的使用处理器的时间
  • 控制终端
  • 文件锁

你可能感兴趣的:(linux,进程)