【Linux】进程的控制

目录

【1】进程创建

【1.1】 fork函数初识

【1.2】fork函数返回值

【1.3】 写时拷贝

【2】进程终止

【2.1】 进程常见退出方法

【2.2】进程常见退出方法

【2.3】 _exit函数

【2.4】 exit函数

【3】进程等待

【3.1】 进程等待必要性

【3.2】进程等待的wait方法

【4】进程程序替换

【4.1】替换原理

【5】结合所学的知识编写一个命令行解释器


        进程在声明周期中有四个阶段:进程创建、进程终止、进程等待、进程程序替换。

【1】进程创建

【1.1】 fork函数初识

        在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。 

#include 
pid_t fork(void);
// 返回值:自进程中返回0,父进程返回子进程id,出错返回-1

        进程调用fork,当控制转移到内核中的fork代码后,内核做:

        分配新的内存块和内核数据结构给子进程。将父进程部分数据结构内容拷贝至子进程。添加子进程到系统进程列表当中。fork返回,开始调度器调度 。

        当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以开始它们自己的旅程,看如下程序。

int main( void )
{
    pid_t pid;
    printf("Before: pid is %d\n", getpid());
    if ( (pid=fork()) == -1 )perror("fork()"),exit(1);
    printf("After:pid is %d, fork return %d\n", getpid(), pid);
    sleep(1);
    return 0;
}
运行结果:
[root@localhost linux]# ./a.out
Before: pid is 43676
After:pid is 43676, fork return 43677
After:pid is 43677, fork return 0

        这里看到了三行输出,一行before,两行after。进程43676先打印before消息,然后它有打印after。另一个after消息有43677打印的。注意到进程43677没有打印before。所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定。

【Linux】进程的控制_第1张图片

 

【1.2】fork函数返回值

子进程返回0。父进程返回的是子进程的pid。

  • 如何理解fork()函数有两个返回值问题?
    • fork()是在内核中实现的,在调用完fork()之后父进程和子进程各自执行return。
  • 如何理解fork()返回之后,给父进程返回子进程的pid,给子进程返回0?
    • 我们把他比如现实生活中:父亲 : 孩子 = 1 : n , n >= 1,孩子只有一个父亲是唯一性,父亲可以有多个孩子。
  • 如何理解同一个id值,怎么可能会保存两个不同的值,让if elseif同时执行?
    • 返回的本质就是写入,所以谁先返回,谁就先写,父进程和子进程各自执行return的!同一个id,都是同一个地址,返回的值不一样。

【1.3】 写时拷贝

写时拷贝就是:当你在写的时候,操作系统会重新给分配一段空间,并且将老的数据拷贝到新的空间中,然后创建页表.

        通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。具体见下图:

【Linux】进程的控制_第2张图片

【1.4】fork常规用法

  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
  • 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数 .

【1.5】 fork调用失败的原因

  • 系统中有太多的进程
  • 实际用户的进程数超过了限制

【2】进程终止

【2.1】 进程常见退出方法

首先第一个问题:我们来说明一下平时我们在写C语言或者C++的时候返回值的return 0;这是代表什么意思呢?

int main()
{
   int num = AddToTarget(1,100);
   // 如何设定main函数的返回值呢?? 如果不关心进程退出码,return 0 即可。                                                                                              
   // 如果未来我们是要关心进程退出码的时候,我们要返回特定的数据来表明特定的错误。
   // 进程退出的时候,对应的退出功能码。
   // 标定进程执行的结果是否正确。
   if(num == 5050)      
        return 0;       
    else                
        return 1;       
                        
    return 0 ;          
}
​
​
​
// 我们可以查看一下最近程序执行后返回的退出功能码:
[ShaXiang@VM-8-14-centos C_LessonExercise]$ echo $?
1
// 为什么第二次打印的时候功能码是0呢? 因为echo也实际上是函数调用。
[ShaXiang@VM-8-14-centos C_LessonExercise]$ echo $?
0
[ShaXiang@VM-8-14-centos C_LessonExercise]$ echo $?
0
[ShaXiang@VM-8-14-centos C_LessonExercise]$
​
// 我们通常会将功能码定义为:0表示成功,非零表示失败,非零具体是几,表示不同的错误。
// 当然一般情况下,退出功能码都必须要有对应的功能码文字描述,
// 我们可以用C语言中的strerror(int errnum)将功能码转换成字符描述.
int main()
{
    for(int i = 0; i<200; i++){
        printf("功能码:%d --> %s\n",i,strerror(i));
    }
                                                                                                                                                                        
   return 0 ;
}
// 代码运行:
功能码:0 --> Success
功能码:1 --> Operation not permitted
功能码:2 --> No such file or directory
功能码:3 --> No such process
功能码:4 --> Interrupted system call
功能码:5 --> Input/output error
功能码:6 --> No such device or address
功能码:7 --> Argument list too long
功能码:8 --> Exec format error
.......

【2.2】进程常见退出方法

正常终止(可以通过 echo $? 查看进程退出码):

  • 从main返回
  • 调用exit
  • 调用_exit

异常退出:

  • ctrl + c 信号终止

【2.3】 _exit函数

_exit()是一个系统接口函数,终止进程,不会刷新缓冲区。

#include 
void _exit(int status);
参数:status 定义了进程的终止状态,父进程通过wait来获取该值
说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值是255。

【2.4】 exit函数

exit()是一个库函数,终止进程,主动刷新缓冲区。

#include void exit(int status);
  #include 
  #include 
  #include 
  
  int main(){
      printf("Hello World");                                                                                                                                                               
      sleep(2);
      exit(1);            // exit是函数接口:最终会将Hello Wrold打印出来退出。
  }


  #include 
  #include 
  #include 
  
  int main(){
      printf("Hello World");                                                                                                                                                               
      sleep(2);
      _exit(1);         // _exit是系统接口:最终不会将Hello Wrold打印出来退出。
  }

总结:

  • exit最后也会调用exit, 但在调用exit之前,还做了其他工作: 执行用户通过 atexit或on_exit定义的清理函数。关闭所有打开的流,所有的缓存数据均被写入调用_exit
  • exit 终止程序,主动刷新缓冲区。
  • _exit 终止程序,不会主动刷新缓冲区。

return退出:return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数。

【Linux】进程的控制_第3张图片

【3】进程等待

【3.1】 进程等待必要性

        之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

【3.2】进程等待的wait方法

#include
#include
pid_t wait(int*status);
返回值:成功返回被等待进程pid,失败返回-1。
参数:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
​
// 输出测试代码
#include 
#include 
#include 
#include 
#include 

int main()
{
    pid_t id = fork();
    
    // 子程序.
    if(id == 0) {
        int cnt = 5;
        while(cnt > 0){
            printf("我是子程序:%d,父进程:%d,cnt:%d\n", getpid(), getppid(), cnt--);
            sleep(1);
        }
        exit(0); // 子程序退出.
    }
    
    // 父进程休眠
    sleep(5);
    int c = 5;
    while((c--) > 0){
        printf("我是父进程:正在休眠中!\n");
        if(c == 0)
            break;
        sleep(1);
    }
   
    printf("我是父进程:开始工作 ");
    pid_t ret = wait(NULL);
    if(ret > 0){
        printf("Wait Success -> %d\n", ret);
    }
    sleep(5);
    printf("我是父进程:工作结束!\n");
    return 0 ;
}

// 监控脚本:
while :; do ps ajx | head -1 && ps ajx | grep myTest | grep -v grep; sleep 1; done
// 验证Z状态等待效果:

【Linux】进程的控制_第4张图片

【3.3】进程等待的waitpid方法

pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
    当正常返回的时候waitpid返回收集到的子进程的进程ID;
    如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
    如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
    pid:
        Pid=-1,等待任一个子进程。与wait等效。
        Pid>0.等待其进程ID与pid相等的子进程。
    status:
        WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
        WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
    options:
        WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

        如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。如果不存在该子进程,则立即出错返回。

  • 获取子进程status
#include 
#include 
#include 
#include 
#include 

int main() {
    pid_t id = fork();
    
    // 子程序.
    if(id == 0) {
        int cnt = 5;
        while(cnt > 0){
            printf("我是子程序:%d,父进程:%d,cnt:%d\n", getpid(), getppid(), cnt--);
            sleep(1);
        }
        exit(10); // 子程序退出.
        // 进程退出分为3 中情况:
        // 一、运行完毕正常退出:
        //              1、代码运行完毕,结果正确
        //              2、代码运行完毕,结果错误
        // 二、运行终止:
        //              3、代码没有运行完毕,异常退出
    }
    
    // 父进程休眠
    sleep(5);
    int c = 5;
    while((c--) > 0){
        printf("我是父进程:正在休眠中!\n");
        if(c == 0)
            break;
        sleep(1);
    }
   
    printf("我是父进程:开始工作 ");
    
    int status = 0;
    pid_t ret = waitpid(id, &status, 0);
    ret = 0;
    if(id > 0){
        printf("Wait Success->status=%d, signel Number=%d, child exit code=%d\n", status, (status & 0x7F), (status >> 8) & 0xFF);
    }
    sleep(5);
    printf("我是父进程:工作结束!\n");
    return 0 ;
}

// 输出结果:Wait Success->status=2560, signel Number=0, child exit code=10

        wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。如果传递NULL,表示不关心子进程的退出状态信息。否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):

进程退出分为3 中情况:

  • 运行完毕正常退出:
    • 代码运行完毕,结果正确
    • 代码运行完毕,结果错误
  • 运行终止:代码没有运行完毕,异常退出

【Linux】进程的控制_第5张图片

如果是错误结束进程呢?将上面的代码替换掉,进程测试结果如下:

// 查看测试效果 - 异常结束(异常除零操作):
[ShaXiang@VM-8-14-centos C_LessonExercise]$ ./myTest 
我是子程序:20082,父进程:20081,cnt:5
wait success:20082 sugNumber:8 childExitCode:0

 // 查看测试效果 - 异常结束(野指针):
[ShaXiang@VM-8-14-centos C_LessonExercise]$ ./myTest 
我是子程序:20082,父进程:20081,cnt:5
wait success:20082 sugNumber:11 childExitCode:0

// 查看错误码:kill -l
[ShaXiang@VM-8-14-centos C_LessonExercise]$ kill -l
 1) SIGHUP     2) SIGINT     3) SIGQUIT     4) SIGILL     5) SIGTRAP
 6) SIGABRT     7) SIGBUS     8) SIGFPE     9) SIGKILL    10) SIGUSR1
11) SIGSEGV    12) SIGUSR2    13) SIGPIPE    14) SIGALRM    15) SIGTERM
16) SIGSTKFLT    17) SIGCHLD    18) SIGCONT    19) SIGSTOP    20) SIGTSTP
21) SIGTTIN    22) SIGTTOU    23) SIGURG    24) SIGXCPU    25) SIGXFSZ
26) SIGVTALRM    27) SIGPROF    28) SIGWINCH    29) SIGIO    30) SIGPWR
31) SIGSYS    34) SIGRTMIN    35) SIGRTMIN+1    36) SIGRTMIN+2    37) SIGRTMIN+3
38) SIGRTMIN+4    39) SIGRTMIN+5    40) SIGRTMIN+6    41) SIGRTMIN+7    42) SIGRTMIN+8
43) SIGRTMIN+9    44) SIGRTMIN+10    45) SIGRTMIN+11    46) SIGRTMIN+12    47) SIGRTMIN+13
48) SIGRTMIN+14    49) SIGRTMIN+15    50) SIGRTMAX-14    51) SIGRTMAX-13    52) SIGRTMAX-12
53) SIGRTMAX-11    54) SIGRTMAX-10    55) SIGRTMAX-9    56) SIGRTMAX-8    57) SIGRTMAX-7
58) SIGRTMAX-6    59) SIGRTMAX-5    60) SIGRTMAX-4    61) SIGRTMAX-3    62) SIGRTMAX-2
63) SIGRTMAX-1    64) SIGRTMAX    
  • 进程的阻塞等待方式:
#include 
#include 
#include 
#include 
#include 
#include 
​
int main() {
    /* 创建子进程 */
    pid_t id = fork();
    assert(id != -1);   /* 确保子进程创建成功 */
​
    /* 子进程延时控制时间,然后退出进程,这时候子进程处于等待,等到父进程休眠结束,就会释放子进程 */
    if(id == 0){
        int count = 10;
        while(count > 0){
            printf("我是子进程id是:%d,父进程id是:%d,剩余时间是:%d\n", getpid(), getppid(), count--);
            sleep(1);
        }
        exit(10);
    }
    // 父进程.
    // No.1 让OS释放子进程的僵尸状态.
    // No.2 获取子进程的退出结果.
    // No.3 在等待期间,子进程没有退出的时间,父进程只能阻塞等待.
    int status = 0;
    // 等待waitpid()调用成功.
    int ret = waitpid(id, &status, 0);
    if(ret != -1){
        // 我们还可以使用系统提供的宏.                                                                                                                                  
        if(WIFEXITED(status)){
            printf("wait success,exit code: %d\n", WEXITSTATUS(status));
        }
        else{
            printf("child exit not normal!\n");
        } 
        // printf("wait success,exit code: %d, sig: %d\n", (status>>8) & 0xFF, status & 0x7F);
    }
    sleep(5);

    return 0;
}

结论:

  • 进程退出会变成僵尸进程,会把自己的退出结果写入到自己的task_struct(PCB)中。
  • wait/waitpid是一个系统调用,OS有资格也有能力去读取子进程的task_struct,OS是从退出子进程的tesk_struct中获取的。

进程的非阻塞等待方式:

#include 
#include 
#include 
#include 
#include 
#include 
​
int main() {
    /* 创建子进程 */
    pid_t id = fork();
    assert(id != -1);   /* 确保子进程创建成功 */
​
​
    /* 子进程 */
    /* 子进程延时控制时间,然后退出进程,这时候子进程处于等待,等到父进程休眠结束,就会释放子进程 */
    if(id == 0){
        int count = 10;
        while(count > 0){
            printf("我是子进程id是:%d,父进程id是:%d,剩余时间是:%d\n", getpid(), getppid(), count--);
            sleep(1);
        }
        exit(10);
    }
    
    /* 父进程 - 非阻塞等待 */
    int status = 0;
    while(1){
        pid_t ret = waitpid(id, &status, WNOHANG);  // WNOHANG:非阻塞等待->子程序没有退出,父进程检测的时候,立即返回。 
        // waitpid调用成功 && 子程序没退出.
        // 子程序没有退出,我的waitpid没有等待失败,仅仅是检测了子程序没退出.                                  
        if(ret == 0){
            printf("waitpid done,but child is running....\n");
        }
        // waitpid调用成功 && 子程序退出了.
        else if(ret > 0){
            printf("waitpid success,exit code: %d, sig: %d\n", (status >> 8) & 0xFF, status & 0x7F);
            break;
        }
        // waitpid调用失败.
        else{
            printf("waitpid call failed!\n");
            break;
        }
        sleep(1);
    }
    return 0;
}

总结:

非阻塞有什么好处呢?

答案:不会占用父进程的所有精力,父进程可以在轮询期间,干别的事情。

示例程序:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define NUM 10
typedef void (*Func_t)(); // 函数指针
// 定义:
Func_t HandlerTask[NUM];

// 样例任务:创建回调函数
void Func_1()
{
    printf("我是Task1!\n");
}
void Func_2()
{
    printf("我是Task2!\n");
}
void Func_3()
{
    printf("我是Task3!\n");
}
void Func_4()
{
    printf("我是Task4!\n");
}
void Func_5()
{
    printf("我是Task5!\n");
}

// 定义加载任务函数
void LoadTask()
{
    memset(HandlerTask, 0, sizeof(HandlerTask));
    HandlerTask[0] = Func_1;
    HandlerTask[1] = Func_2;
    HandlerTask[2] = Func_3;
    HandlerTask[3] = Func_4;
    HandlerTask[4] = Func_5;
}

int main()
{
    pid_t id = fork();
    assert(id != -1);

    // 子程序.
    if (id == 0)
    {
        int cnt = 6;
        while (cnt > 0)
        {
            printf("我是子程序:%d,父进程:%d,cnt:%d\n", getpid(), getppid(), cnt--);
            sleep(1);
        }
        exit(10); // 子程序退出.
    }

    printf("我是父进程:开始工作! \n");
    LoadTask();
    int status = 0;
    while (1)
    {
        sleep(2);
        pid_t ret = waitpid(id, &status, WNOHANG);
        if (ret == 0)
        {
            printf("waitpid done,but child is running....\n");
            // 父进程在空闲时,执行规定好的任务.
            for (int i = 0; HandlerTask[i] != NULL; i++)
            {
                HandlerTask[i](); // 采用回调函数的方式,执行我们想要让父进程在空间的时候做的事情.
            }
        }
        else if (ret > 0)
        {
            printf("waitpid success,exit code: %d, sig: %d\n", (status >> 8) & 0xFF, status & 0x7F);
            break;
        }
        else
        {
            printf("waitpid call failed!\n");
            break;
        }
    }
    printf("父进程已经待到子进程结束,处理中......\n");
    sleep(5);
    printf("我是父进程:工作结束!\n");
    return 0;
}

// 打印结果: >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[shaxiang@VM-8-14-centos lesson]$ ./myTest 
我是父进程:开始工作! 
我是子程序:30058,父进程:30057,cnt:6
我是子程序:30058,父进程:30057,cnt:5
waitpid done,but child is running....
我是Task1!
我是Task2!
我是Task3!
我是Task4!
我是Task5!
我是子程序:30058,父进程:30057,cnt:4
我是子程序:30058,父进程:30057,cnt:3
waitpid done,but child is running....
我是Task1!
我是Task2!
我是Task3!
我是Task4!
我是Task5!
我是子程序:30058,父进程:30057,cnt:2
我是子程序:30058,父进程:30057,cnt:1
waitpid done,but child is running....
我是Task1!
我是Task2!
我是Task3!
我是Task4!
我是Task5!
waitpid success,exit code: 10, sig: 0
父进程已经待到子进程结束,处理中......
我是父进程:工作结束!

【4】进程程序替换

【4.1】替换原理

        用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变 。

【Linux】进程的控制_第6张图片

 

【4.2】替换函数

        其实有六种以exec开头的函数,统称exec函数:

#include `
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[]); 
// 以上函数接口:将程序加载到内存中,让指定进程进行执行

命名理解:

        这些函数原型看起来很容易混,但只要掌握了规律就很好记。

  • l -> (list) : 表示参数采用列表(list 将参数一个一个的传入exec*)
  • v -> (vector) : 参数用数组(可以将所有的执行参数放入到数组中,同一传递,而不需要在使用可变参数的方案)
  • p -> (path) : 有p自动搜索环境变量PATH(如何找到程序的功能,带p字符的函数,不用告诉我程序的路径,只需要告诉我是谁,我会自动在环境变量PATH中查找)
  • e -> (env) : 表示自己维护环境变量
int execl(const char *path, const char *arg, ...);

函数解释

        这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。如果调用出错则返回-1所以exec函数只有出错的返回值而没有成功的返回值。

写一段程序,我们开看一下猪跑:

#include 
#include 
​
int main()
{
    // .c -> exe -> load -> process -> 运行 -> 执行我们现在所写的代码.
    printf("process is runing...\n");
​
    // execl(/*要执行的程序*/"/usr/bin/ls","ls",/*你想怎样执行*/NULL);
    execl(/*要执行的程序*/"/usr/bin/ls",/*你想怎样执行*/"ls","--color=auto","-a","-l",NULL);
    // execl 只要是一个函数,就有可能会调用失败,如果没有替换成功,就是没有替换。
    // execl返回值只有在失败的时候返回-1,那么为什么没有成功的返回值呢?因为成功了就和接下来的代码无关了,判断毫无意义。
    // execl为啥没有成功的返回值呢?因为成功了,就和接下来的代码无关了,判断毫无意义!
                                                                                                                                                                        
    printf("process runing done...\n");
    return 0;
}

// 打印结果:
[shaxiang@VM-8-14-centos lesson]$ ./MyExecl 
process is runing...
total 28
drwxrwxr-x  2 shaxiang shaxiang 4096 May 21 09:57 .
drwx------ 12 shaxiang shaxiang 4096 Apr 28 21:55 ..
-rw-rw-r--  1 shaxiang shaxiang   81 May 21 09:56 Makefile
-rwxrwxr-x  1 shaxiang shaxiang 8408 May 21 09:57 MyExecl
-rw-rw-r--  1 shaxiang shaxiang  211 May 21 09:57 MyExecl.c
// execl将程序替换掉了,如果execl替换失败 printf("process runing done...\n");也是会被打印出来的。
  • 在使用execl时一般都是调用子进程进行替换代码:
  • int execl(const char *path, const char *arg, ...);
#include 
#include 
#include 
#include 
#include 
#include 
​
int main()
{
    // 创建子进程.
    pid_t id = fork();
    assert(id != -1);
​
    if(id == 0){
        sleep(1);
        execl("/usr/bin/ls", "ls", "-a", "-l", "--color=auto", NULL);    // 这里的替换不会影响父进程的,进程具有独立性!
        if(ret == -1)
            printf("execl()替换失败!\n");
        exit(-1);    // execl如果替换成功,exit(1)是不会进行执行的
    }
​
​
    // 父进程
    int status = 0;                                                                                                                                                     
    pid_t ret = waitpid(id, &status, 0);
    if(ret > 0)
        printf("wait success -> exit code:%d, sig:%d\n", (status >> 8) & 0xFF, status & 0x7F);;
​
    return 0;
}
  • int execlp(const char *file, const char *arg, ...);
#include 
#include 
#include 
#include 
#include 
#include 
​
int main()
{
    // 创建子进程.
    pid_t id = fork();
    assert(id != -1);
​
    if(id == 0){
        sleep(1);
        execlp("ls", "ls", "-a", "-l", "--color=auto", NULL);                                                                                                           
        exit(-1);
    }
​
​
    // 父进程
    int status = 0;
    pid_t ret = waitpid(id, &status, 0);
    if(ret > 0)
        printf("wait success -> exit code:%d, sig:%d\n", (status >> 8) & 0xFF, status & 0x7F);;
​
    return 0;
}
  • int execv(const char *path, char *const argv[]);
#include 
#include 
#include 
#include 
#include 
#include 
​
int main()
{
    // 创建子进程.
    pid_t id = fork();
    assert(id != -1);
​
    if(id == 0){
        sleep(1);
​
        char* const argv_[] = {
            "ls",
            "-a",
            "-l",
            "--color=auto",
            NULL
        };
​
        execv("/usr/bin/ls", argv_);
        exit(-1);
    }
                                                                                                                                                                      
    // 父进程
    int status = 0;
    pid_t ret = waitpid(id, &status, 0);
    if(ret > 0)                         
        printf("wait success -> exit code:%d, sig:%d\n", (status >> 8) & 0xFF, status & 0x7F);;
                                                                                               
    return 0;
}
  • int execvp(const char *file, char *const argv[]);
#include 
#include 
#include 
#include 
#include 
#include 
​
int main()
{
    // 创建子进程.
    pid_t id = fork();
    assert(id != -1);
​
    if(id == 0){
        sleep(1);
​
        char* const argv_[] = {
            "ls",
            "-a",
            "-l",
            "--color=auto",
            NULL
        };
​
        execvp("ls", argv_);                                                                     
        exit(-1);
    }
​
    // 父进程
    int status = 0;
    pid_t ret = waitpid(id, &status, 0);
    if(ret > 0)
        printf("wait success -> exit code:%d, sig:%d\n", (status >> 8) & 0xFF, status & 0x7F);;
​
    return 0;
}
  • int execle(const char *path, const char *arg, ...,char *const envp[]);
#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
    pid_t id = fork();
    assert(id != -1);

    if (id == 0)
    {
        // 子进程进行程序替换.
        printf("我是子进程:正在进行程序替换中....\n");
        sleep(1);
        
        // char *const envp_[] = {
        //    (char *)"EYENV=1122334455",
        //    NULL
        // };
        execle("./myC", "myC", NULL, envp_); // 自定义的环境变量.
        
        extern char **environ;
        execle("./myC", "myC", NULL, environ); // 系统的的环境变量.
        // 退出程序!
        exit(1);
    }

    // 父进程.....
    int status = 0;
    pid_t ret = waitpid(id, &status, 0);
    if (ret > 0)
        printf("wait success -> signel number=%d, exit code=%d\n", (status & 0x7F), (status >> 8) & 0xFF);

    return 0;
}
  • 总结:

函数名

参数格式

是否带路径

是否使用当前环境变量

execl

列表

不是

execlp

列表

execle

列表

不是

不是,必须自己组装环境变量

execv

数组

不是

execvp

数组

exeve

数组

不是

不是,不许自己组装环境变量

#include 
int main()
{
    char *const argv[] = {"ps", "-ef", NULL};
    char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
    execl("/bin/ps", "ps", "-ef", NULL);
    // 带p的,可以使用环境变量PATH,无需写全路径
    execlp("ps", "ps", "-ef", NULL);
    // 带e的,需要自己组装环境变量
    execle("ps", "ps", "-ef", NULL, envp);
    execv("/bin/ps", argv);
    // 带p的,可以使用环境变量PATH,无需写全路径
    execvp("ps", argv);
    // 带e的,需要自己组装环境变量
    execve("/bin/ps", argv, envp);
    exit(0);
}

        事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在man手册第3节 。

  • 使用execl调用自己的程序.
  • 目录下的文件结构
// 目录下的文件结构:
[ShaXiang@VM-8-14-centos exec]$ ll -al
total 44
drwxrwxr-x 2 ShaXiang ShaXiang 4096 Jan 23 15:32 .
drwxrwxr-x 3 ShaXiang ShaXiang 4096 Jan 23 13:17 ..
-rw-rw-r-- 1 ShaXiang ShaXiang  190 Jan 23 15:33 Makefile
-rwxrwxr-x 1 ShaXiang ShaXiang 8360 Jan 23 15:32 myBin
-rw-rw-r-- 1 ShaXiang ShaXiang  490 Jan 23 15:32 myBin.c    // 被调用程序
-rwxrwxr-x 1 ShaXiang ShaXiang 8720 Jan 23 15:31 myExec
-rw-rw-r-- 1 ShaXiang ShaXiang  518 Jan 23 15:31 myExec.c  // 调用程序
  • Makefile文件
// 使用Makefile构建:
.PHONY :all
all:myExecl myC 

myExecl:MyExecl.c
    gcc -o $@ $^ -std=c99 


myC:MyC.c
    gcc -o $@ $^ -std=c99 

.PHONY:clean
clean:
    rm -f myExecl   
    rm -f myC
  • 被调用的文件
// 额外的C程序》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》
#include 
int main(){
    printf("我是另一个C 程序!\n");
    printf("我是另一个C 程序!\n");
    printf("我是另一个C 程序!\n");
    printf("我是另一个C 程序!\n");
    printf("我是另一个C 程序!\n");
    printf("我是另一个C 程序!\n");
    printf("我是另一个C 程序!\n");
    printf("我是另一个C 程序!\n");
    printf("我是另一个C 程序!\n");
    return 0;
}

// 额外的Cpp程序》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》
#include 
using namespace std;
int main(){
    cout << "我是一个C++ 程序!" << endl;
    cout << "我是一个C++ 程序!" << endl;
    cout << "我是一个C++ 程序!" << endl;
    cout << "我是一个C++ 程序!" << endl;
    cout << "我是一个C++ 程序!" << endl;
    cout << "我是一个C++ 程序!" << endl;
    cout << "我是一个C++ 程序!" << endl;
    return 0;
}

// 额外的Shell程序》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》
#!/bin/bash  
echo "Hello Shell!"
echo "Hello Shell!"
echo "Hello Shell!"
echo "Hello Shell!"
echo "Hello Shell!"
echo "Hello Shell!"
echo "Hello Shell!"
echo "Hello Shell!"
echo "Hello Shell!"

程序替换:可以使用程序替换,调用任何后端语言对应的可执行程序。

【Linux】进程的控制_第7张图片

【5】结合所学的知识编写一个命令行解释器

#include 
#include 
#include  
#include 
#include 
#include 
#include 
​
#define NUM 1024
#define OPT_NUM 64
​
char lineCommand[NUM];
char* myArgv[OPT_NUM];
int lastCode = 0;
int lastSig = 0;
​
int main(){
   
    while(1)
    {
        /* 模拟输出Shell的提示符号: */
        printf("用户名@主机名 当前路径#:");
    
        /* 立即刷新到显示器上 */
        fflush(stdout);
    
        /* 获取用户输入,例如输入的ls -a -l (注意:获取用户的输入会带\n,所以这里要处理一下\n) */
        char* inputS = fgets(lineCommand, sizeof(lineCommand ) - 1, stdin);
        assert(inputS != NULL);
        (void)inputS;
        // 处理\n
        lineCommand[strlen(lineCommand) - 1] = 0;                                                        
​
#ifdef DEBUG 
        // 查看一下获取到用户输入的字符串.
        printf("test: %s\n", lineCommand);
#endif
        
        /* 获取用户输入的字符串类似:ls -a -l 这种 */
        /* 需要对这样的字符串进行分割(利用strtok()函数进行分割) */
        myArgv[0] = strtok(lineCommand, " ");
        int i = 1;
        // 这里判断如果是特殊的指令,进行特殊处理.
        if(myArgv[0] != NULL && strcmp(myArgv[0], "ls") == 0){                                              
            myArgv[i++] = (char*)"--color=auto";
        }
    
        while(myArgv[i++] = strtok(NULL, " "));
        // 如果是cd命令,不需要创建子进程,让shell自己执行对应的命令,本质就是执行系统接口.
        // 像这种不需要让我们的子进程来执行,而是让shell自己执行的命令,称为内建/内置命令.
        if(myArgv[0] != NULL && strcmp(myArgv[0], "cd") == 0){
            if(myArgv[1] != NULL)
                chdir(myArgv[1]);
            continue;
        }
        if(myArgv[0] != NULL && myArgv[1] != NULL && strcmp(myArgv[0], "echo") == 0){
            if(strcmp(myArgv[1], "$?") == 0){
                printf("%d,%d\n", lastCode, lastSig);
            }
            else{
                printf("%s\n", myArgv[1]);
            }
            continue;
        }
​
#ifdef DEBUG 
        for(int j = 0; myArgv[j]; j++){
            printf("myArgv[%d]:%s\n", j , myArgv[j]);
        }
#endif 
        // 执行命令:
        pid_t id = fork();
        assert(id != -1);
   
        // 子程序.
        if(id == 0){
            execvp(myArgv[0], myArgv);
            exit(1);
        }
​
        // 父进程.
        int status = 0;
        pid_t ret = waitpid(id, &status, 0);
        assert(ret > 0);
        (void) ret;
        
        lastCode = ((status >> 8) & 0xFF);
        lastSig = (status & 0x7F);
    } 
​
    return 0;
}

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