Linux进程实践(3) --进程终止与exec函数族

进程的几种终止方式

(1)正常退出

从main函数返回[return]

调用exit

调用_exit/_Exit

(2)异常退出

调用abort   产生SIGABOUT信号

由信号终止  Ctrl+C [SIGINT]

...(并不完全, 如return/pthread_exit等)

测试[exit/_exit]

//尝试查看该程序的打印输出
int main()
{
    cout << "In main, pid = " << getpid();
    //去掉了endl;
    //原理:与终端关联,stdout为行缓冲,在文件中,为全缓冲;
    //详细信息请参考《UNIX环境高级编程》(第三版)8.5节, P188
    //exit(0);为C库函数,详细解释如下
    _exit(0);
}

由图可知,系统调用_exit直接陷入内核,而C语言库函数是经过一系列的系统清理工作,再调用Linux内核的;

int main()
{
    cout << "In main, pid = " << getpid();
    fflush(stdout);	//增加了刷新缓冲区工作
    _exit(0);
}

小结:exit与_exit区别

1)_exit是一个系统调用,exit是一个c库函数

2)exit会执行清除I/O缓存

3)exit会执行调用终止处理程序 //终止处理程序如下

终止处理程序:atexit

#include <stdlib.h>
int atexit(void (*function)(void));
//测试
void exitHandler1(void)
{
    cout << "If exit with exit, the function exitHandler will be called1" << endl;
}
void exitHandler2(void)
{
    cout << "If exit with exit, the function exitHandler will be called2" << endl;
}

int main()
{
    cout << "In main, pid = " << getpid() << endl;
    atexit(exitHandler1);	//注意,先注册的后执行
    atexit(exitHandler2);
    exit(0);
}

异常终止

int main()
{
    cout << "In main, pid = " << getpid() << endl;
    atexit(exitHandler1);
    atexit(exitHandler2);
    abort();
    //exit(0);
}

exec函数族

exec替换进程印象

在进程的创建上,Unix采用了一个独特的方法,它将进程创建与加载一个新进程映象分离。这样的好处是有更多的余地对两种操作进行管理。

当我们创建了一个进程之后,通常将子进程替换成新的进程映象,这可以用exec系列的函数来进行。当然,exec系列的函数也可以将当前进程替换掉。

exec只是用磁盘上的一个新程序替换了当前进程的正文段, 数据段, 堆段和栈段.

函数族信息

#include <unistd.h>
int execve(const char *filename, char *const argv[],
                  char *const envp[]);

#include <unistd.h>
extern char **environ;
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 execvpe(const char *file, char *const argv[],
            char *const envp[]);

说明:

execl,execlp,execle(都带“l”, 代表list)的参数个数是可变的,参数以必须一个空指针结束。

execv和execvp的第二个参数是一个字符串数组(“v”代表“vector”,字符串数组必须以NULL结尾),新程序在启动时会把在argv数组中给定的参数传递到main。

名字最后一个字母是“p”的函数会搜索PATH环境变量去查找新程序的可执行文件。如果可执行文件不在PATH定义的路径上,就必须把包括子目录在内的绝对文件名做为一个参数传递给这些函数;

/*总结:l代表可变参数列表,p代表在path环境变量中搜索file文件。envp代表环境变量*/

//示例execlp
int main()
{
    pid_t pid = fork();
    if (pid == 0)
    {
        if (execlp("/bin/pwd", "pwd", NULL) == -1)
            err_exit("execlp pwd error");
    }
    wait(NULL);

    pid = fork();
    if (pid == 0)
    {
        if (execlp("/bin/ls", "ls", "-l", NULL) == -1)
            err_exit("execlp ls -l error");
    }
    wait(NULL);
    cout << "After execlp" << endl;
}
//示例execve
int main()
{
    char *const args[] =
    {
        (char *)"/bin/date",
        (char *)"+%F",
        NULL
    };
    execve("/bin/date",args,NULL);
    cout << "After fork..." << endl;

    return 0;
}
//示例execle
//1:main.cpp
int main()
{
    cout << "In main, pid = " << getpid() << endl;

    char *const environ[] =
    {
        "AA=11",
        "BB=22",
        "CC=33",
        NULL
    };
    execle("./hello","./hello",NULL,environ);	//当environ填为NULL时,则什么都不传递
    cout << "After fork..." << endl;

    return 0;
}
extern char **environ;
int main()
{
    cout << "In hello, pid = " << getpid() << endl;

    cout << "environ:" << endl;
    for (int i = 0; environ[i] != NULL; ++i)
    {
        cout << "\t" << environ[i] << endl;
    }
}
/*
In main, pid = 3572	//PID保持不变
In hello, pid = 3572
environ:
	AA=11
	BB=22
	CC=33
*/

//示例: execve 与 execlp
int main()
{
    pid_t pid = fork();
    if (pid == -1)
        err_exit("fork error");
    else if (pid == 0)
    {
        //示例execve
        char *const args[] =
        {
            "echoall",
            "myarg1",
            "MY ARG2",
            NULL
        };
        char *const env[] =
        {
            "USER=unknown",
            "PATH=/tmp",
            NULL
        };

        execve("./echoall",args,env);
    }
    wait(NULL);

    pid = fork();
    if (pid == -1)
        err_exit("fork error");
    else if (pid == 0)
    {
        //示例execlp
        execlp("./echoall", "echoall", "only one arg", NULL);
    }
    wait(NULL);

    return 0;
}

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

    for (char **ptr = environ; *ptr != NULL; ++ ptr)
        printf("%s\n", *ptr);

    exit(0);
}

System系统调用

system()函数调用“/bin/sh -c command”执行特定的命令,阻塞当前进程直到command命令执行完毕,system函数执行时,会调用fork、execve、waitpid等函数。

原型:

int system(const char *command);

返回值:

如果无法启动shell运行命令,system将返回127;出现不能执行system调用的其他错误时返回-1。如果system能够顺利执行,返回那个命令的退出码。

//示例
int main()
{
    system("ls -la");

    return 0;
}

自己动手写system

int mySystem(const char *command)
{
    if (command == NULL)
    {
        errno = EAGAIN;
        return -1;
    }
    pid_t pid = fork();
    if (pid == -1)
    {
        perror("fork");
        exit(-1);
    }
    else if (pid == 0)
    {
        execl("/bin/sh","sh","-c",command,NULL);
        exit(127);
    }

    int status;
    waitpid(pid,&status,0);
    //wait(&status);

    return WEXITSTATUS(status);
}

int main()
{
    mySystem("ls -la");

    return 0;
}

时间: 2024-08-07 13:48:05

Linux进程实践(3) --进程终止与exec函数族的相关文章

Linux系统编程——进程替换:exec 函数族

在 Windows 平台下.我们能够通过双击运行可运行程序.让这个可运行程序成为一个进程:而在 Linux 平台.我们能够通过 ./ 运行,让一个可运行程序成为一个进程. 可是,假设我们本来就执行着一个程序(进程),我们怎样在这个进程内部启动一个外部程序.由内核将这个外部程序读入内存,使其执行起来成为一个进程呢?这里我们通过 exec 函数族实现. exec 函数族.顾名思义.就是一簇函数,在 Linux 中,并不存在 exec() 函数.exec 指的是一组函数.一共同拥有 6 个: [cpp

进程和程序:编写shell——《Unix/Linux编程实践教程》读书笔记(第8章)

1.Unix shell的功能 shell是一个管理进程和运行程序的程序.所有常用的shell都有3个主要功能: (1)运行程序: (2)管理输入和输出 (3)可编程 shell同时也是带有变量和流程控制的编程语言. 2.Unix的进程模型 一个程序是存储在文件中的机器指令序列,一般它是由编译器将源代码编译成二进制格式的代码.运行一个程序意味着将这些机器指令序列载入内存然后让处理器(CPU)逐条执行.在Unix术语中,一个可执行程序是一些机器指令机器数据的序列.一个进程是程序运行时的内存空间和设

Linux进程实践(1) --Linux进程编程概述

进程 VS. 程序 什么是程序? 程序是完成特定任务的一系列指令集合. 什么是进程? [1]从用户的角度来看:进程是程序的一次执行过程 [2]从操作系统的核心来看:进程是操作系统分配的内存.CPU时间片等资源的基本单位. [3]进程是资源分配的最小单位 [4]每一个进程都有自己独立的地址空间与执行状态. [5]像UNIX这样的多任务操作系统能够让许多程序同时运行,每一个运行着的程序就构成了一个进程 进程数据结构 进程由三部分组成:PCB.程序段和数据段. 进程控制块PCB:用于描述进程情况及控制

Linux环境编程之进程(一):main函数调用、进程终止以及命令行参数和环境表

(一)main函数调用 main函数作为程序运行时的入口函数,它是如何被调用的呢?首先必须清楚一点,main函数也是一个函数,它只有被调用才能够执行.其实,在执行可执行程序时,在调用main函数之前,内核会先调用一个特殊的启动例程,将此启动例程作为可执行程序的起始地址.启动例程是如何作为可执行程序的起始地址的?这是由链接编译器设置的,而链接编译器则是由C编译器(如gcc编译器)调用的.启动例程作为可执行程序的起始地址主要做哪些工作呢?启动例程从内核取得命令行参数和环境变量值,以此来为main函数

Linux环境编程之进程(四):创建新进程、执行程序和进程终止

引言: 对于每个进程,都有一个非负整数表示的唯一进程ID.虽然进程的ID是唯一的,但却是可重用的.系统中有一些专用的进程.如ID为0的进程通常是调度进程,也成交换进程或系统进程(它是内核进程).进程ID为1通常是init进程,它是一个普通的用户进程.一些与进程ID有关的函数: #include <unistd.h> pid_t getpid(void);   //返回值:调用进程的进程ID pit_t getppid(void); //返回值:调用进程的父进程ID uid_t getuid(v

Linux环境编程之进程(五):竞争条件以及exec函数

(一) 当多个进程企图对共享数据进行某种处理,而最后的结果又取决于进程运行的顺序时,就认为它们发生了竞争关系.避免竞争的条件,给出apue上的一个代码吧: #include "apue.h" static void charatatime(char *); int main(void) { pid_t pid; TELL_WAIT(); /*set things up for TELL_XXX & WAIT_XXX*/ if((pid == fork()) < 0){ e

Linux下进程的创建(system(); fork(); exec*())

0. system(); system()函数通过调用shell程序来执行所指向的命令(效率低),相当于先fork(),再execve(): 特点:原进程和子进程各自运行,且原进程需要等子进程运行完后再继续: 1. fork(); 参考文献: linux中fork同时创建多个子进程的方法(一) 在Linux中用fork()由一个父进程创建同时多个子进程的格式如下: 1 int status,idx; 2 3 for (idx = 0; idx < 10; idx++) { 4 status =

Linux常用指令---kill | killall(终止进程)

kill Linux中的kill命令用来终止指定的进程(terminate a process)的运行,是Linux下进程管理的常用命令.通常,终止一个前台进程可以使用Ctrl+C键,但是,对于一个后台进程就须用kill命令来终止,我们就需要先使用ps/pidof/pstree/top等工具获取进程PID,然后使用kill命令来杀掉该进程.kill命令是通过向进程发送指定的信号来结束相应进程的.在默认情况下,采用编号为15的TERM信号.TERM信号将终止所有不能捕获该信号的进程.对于那些可以捕

Linux进程控制——exec函数族

原文:http://www.cnblogs.com/hnrainll/archive/2011/07/23/2114854.html 1.简介 在Linux中,并不存在exec()函数,exec指的是一组函数,一共有6个,分别是: #include <unistd.h> extern char **environ; int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char