linux进程控制【万字解析,全网最细!!!】

一、进程创建

1.1 fork函数初识

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

#include <unistd.h>
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之后,谁先执行完全由调度器决定 。

1.2 fork函数返回值

  • 子进程返回0,
  • 父进程返回的是子进程的pid

1. 如何理解fork()函数有两个返回值问题?
在这里插入图片描述

2. 如何理解fork()返回之后,给父进程返回子进程pid,给子进程返回0?
答:唯一性,父亲:孩子 = 1 : n,n >= 1。因为父亲要知道是哪个子进程,所以要给父进程返回子进程pid。
3. 如何理解同一个id值,怎么可能会保存两个不同的值,让if else if 同时执行?
在这里插入图片描述

1.3 写时拷贝(写的时候拷贝)

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

1.4 fork常规用法

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

1.5 fork调用失败的原因

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

二、进程终止

为什么有退出码?
!0具体是几,表示不同的错误。一般而言,退出码都必须有对应的文字描述。

2.1 进程退出场景

  • 代码运行完毕,结果正确。(退出码为0)
  • 代码运行完毕,结果不正确。(退出码为非0)//退出码这个时候起效果。
  • 代码异常终止。退出码无意义。

2.2 进程常见退出方法

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

  1. main函数return返回。
  2. 任意地方调用exit(code)。//库函数,exit终止进程,主动刷新缓冲区
  3. _exit(code)。//系统调用,_exit终止进程,不会主动刷新缓冲区

缓冲区在哪里?---- 用户级的缓冲区
在这里插入图片描述

异常退出:

  • ctrl + c,信号终止。

2.3 _exit函数

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

2.4 exit函数

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

exit最后也会调用exit, 但在调用exit之前,还做了其他工作:

  1. 执行用户通过 atexit或on_exit定义的清理函数。
  2. 关闭所有打开的流,所有的缓存数据均被写入。
  3. 调用_exit。

实例:

int main()
{
printf("hello");
exit(0);
}
运行结果:
[root@localhost linux]# ./a.out
hello[root@localhost linux]#
int main()
{
printf("hello");
_exit(0);
}
运行结果:
[root@localhost linux]# ./a.out
[root@localhost linux]#

2.5 return退出

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

三、进程等待

3.1 进程等待必要性

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

3.2 进程等待的方法

3.2.1 wait方法

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

示例代码:

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include <unistd.h>
  4 #include<string.h>
  5 #include<sys/types.h>
  6 #include<sys/wait.h>
  7 int main()
  8 {
  9     pid_t id = fork();
 10     if(id == 0)
 11     {
 12         //子进程
 13         int cnt = 10;
 14         while(cnt)
 15         {
 16             printf("我是子进程:%d,父进程:%d, cnt:%d\n", getpid(),getppid(),cnt--);
 17             sleep(1);
 18         }
 19         exit(0);//进程退出。
 20     }
 21     //父进程
 22     sleep(15);
 23     pid_t ret = wait(NULL);//进程等待
 24     if(id > 0)
 25     {
 26         printf("wait success:%d\n",ret);
 27     }                                                                                              
 28 }

输出结果为:
在这里插入图片描述

3.2.2 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,子进程存在且正常运行,则进程可能阻塞。
  • 如果不存在该子进程,则立即出错返回。

3.3 获取子进程status

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

测试代码:

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include <unistd.h>
  4 #include<string.h>
  5 #include<sys/types.h>
  6 #include<sys/wait.h>
  7 int main()
  8 {
  9     pid_t id = fork();
 10     if(id == 0)
 11     {
 12         //子进程
 13         int cnt = 5;
 14         while(cnt)
 15         {
 16             printf("我是子进程:%d,父进程:%d, cnt:%d\n", getpid(),getppid(),cnt--);
 17             sleep(1);
 18         }
 19         //运行完
 20         //1.代码完,结果对
 21         //2.代码完,结果不对
 22         //代码没跑完,出异常了
 23         exit(10);//进程退出。
 24     }
 25     //父进程
 26     sleep(5);
 27     int status = 0;//不是被整体使用的,有自己的位图结构。
 28     //pid_t ret = wait(&status);
 29     pid_t ret = waitpid(id, &status, 0);//阻塞式等待                                               
 30     if(id > 0)
 31     {
 32         printf("wait success:%d, sig number/*进程信号*/:%d, child exit code/*子进程退出码*/:%d\n",r    et,(status & 0x7f), (status >> 8) & 0xff);
 33         //printf("wait success:%d\n", ret);
 34     }
 35     sleep(5);
 36 }

测试结果:
在这里插入图片描述

3.4 再谈进程退出

  1. 进程退出会变成僵尸 ------ 会把自己的退出结果写入到自己的task_struct。
  2. wait/waitpid是一个系统调用 --> os -->os有资格也有能力去读取子进程的task_struct。
  3. 都是从退出子进程的task_struct中获取的。

3.5 具体代码实现

阻塞vs非阻塞

  1. 不挂电话,检测李四的状态 — 阻塞
  2. 张三给李四打电话,本质是状态检测,如果没有就绪,直接返回。 – 每一次都是非阻塞等待 – 多次非阻塞等待 – 轮询 — 非阻塞
    非阻塞有什么好处?不会占用父进程的所有精力,可以在轮询期间,干干别的。
  • 进程的阻塞等待方式:
    实例:
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include <unistd.h>
  4 #include<string.h>
  5 #include<sys/types.h>
  6 #include<sys/wait.h>
  7 int main()
  8 {
  9     pid_t id = fork();
 10     if(id == 0)
 11     {
 12         //子进程
 13         int cnt = 5;
 14         while(cnt)
 15         {
 16             printf("我是子进程:%d,父进程:%d, cnt:%d\n", getpid(),getppid(),cnt--);
 17             //int *p;
 18             //*p = 100;//野指针问题
 19             sleep(1);
 20         }
 21         //运行完
 22         //1.代码完,结果对
 23         //2.代码完,结果不对
 24         //代码没跑完,出异常了
 25         exit(10);//进程退出。
 26     }
 27     //父进程
 28     sleep(5);
 29     int status = 0;//不是被整体使用的,有自己的位图结构。
 30     //pid_t ret = wait(NULL);
 31     pid_t ret = waitpid(id, &status, 0);//阻塞式等待                                                                          
 33     if(ret > 0)
 34     {
 35         //是否正常退出
 36         if(WIFEXITED(status))
 37         {
 38             //判断子进程运行结果是否ok
 39             printf("exit code:%d\n", WEXITSTATUS(status));
 40         }
 41     }
 47     return 0;
 48 }

结果:
在这里插入图片描述

  • 进程的非阻塞等待方式:
    实例:
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include <unistd.h>
  4 #include<string.h>
  5 #include<sys/types.h>
  6 #include<sys/wait.h>
  7 int main()
  8 {
  9     pid_t id = fork();
 10     if(id == 0)
 11     {
 12         //子进程
 13         int cnt = 10;
 14         while(cnt)
 15         {
 16             printf("我是子进程:%d,父进程:%d, cnt:%d\n", getpid(),getppid(),cnt--);
 17             //int *p;
 18             //*p = 100;//野指针问题
 19             sleep(3);                                                                              
 20         }
 21         //运行完
 22         //1.代码完,结果对
 23         //2.代码完,结果不对
 24         //代码没跑完,出异常了
 25         exit(10);//进程退出。
 26     }
 27     //父进程
 28     int status = 0;//不是被整体使用的,有自己的位图结构。
 29     //pid_t ret = wait(NULL);
 30     //pid_t ret = waitpid(id, &status, 0);//阻塞式等待
 31     while(1)
 32     {
 33         pid_t ret = waitpid(id, &status, WNOHANG);//非阻塞式等待-》子进程没有推出子进程没有退出,父    进程检测的时候,立即返回。
 34         if(ret == 0)
 35         {
 36             //waitpid 调用成功 && 子进程没有退出
 37             //子进程没有退出,我的waitpid没有等待失败,仅仅是监测到了子进程没有退出
 38             printf("wait done, but child is running...\n");
 39         }
 40         else if(ret > 0)
 41         {
 42             //waitpid调用成功 && 子进程退出了
 43             printf("wait success:%d, sig number/*进程信号*/:%d, child exit code/*子进程退出码*/:%d\    n",ret,(status & 0x7f), (status >> 8) & 0xff);
 44             break;
 45         }
 46         else
 47         {
 48             //waitpid调用失败
 49             printf("waitpid call failed\n");
 50             break;
 51         }
 52         sleep(1);
 53     }
 54     return 0;
 55 }

结果:
在这里插入图片描述

四、进程程序替换

  1. 创建子进程的目的?
    a. 想让子进程执行父进程的一部分 — 执行父进程对应的磁盘代码中的一部分。
    b. 想让子进程指一个全新的程序 — 让子进程想办法,加载磁盘上指定的程序,执行新程序的代码和数据 ---------- 进程的程序替换。

4.1 替换原理

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变.
在这里插入图片描述
在这里插入图片描述
由上图可知,虚拟地址空间 + 页表保证独立性,一旦有执行流像替换代码或者数据,发生写实拷贝。

4.2 替换函数

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

#include <unistd.h>`
1. int execl(const char *path, const char *arg, ...);

示例:

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 int main()
  4 {
  5     //.c-> load -> process -> 运行 -> 执行我们现在写的代码
  6     printf("process is running...\n");
  7     execl("/usr/bin/ls","ls","--color=auto","-a","-l",NULL);
  8     printf("process is running done...\n");                                                        
  9     return 0;
 10 }

结果:
在这里插入图片描述

2.  int execlp(const char *file, const char *arg, ...);

示例:

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 #include<assert.h>
  5 #include<sys/types.h>
  6 #include<sys/wait.h>
  7 int main()
  8 {
  9     printf("process is running...\n");
 10     pid_t id = fork();
 11     assert(id != -1);
 12 
 13     if(id == 0)
 14     {
 15         //这里的替换不会影响父进程,因为进程具有独立性
 16         sleep(1);
 17         //execl("/usr/bin/ls","ls","-a","-l","--color=auto",NULL);
 18         execlp("ls","ls","-a","-l","--color=auto",NULL);                                           
 19         exit(1);
 20     }
 21     int status = 0;
 22     pid_t ret = waitpid(id, &status, 0);
 23     if(ret > 0)
 24     {
 25         printf("wait success:exit code:%d, sig number:%d\n", (status >> 8) & 0xff, status & 0x7f);
 26     }
 27    return 028    }

结果:

在这里插入图片描述

3. int execle(const char *path, const char *arg, ...,char *const envp[]);

示例:
mybin.c

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 int main()
  4 {
  5     printf("PATH:%s\n", getenv("PATH"));
  6     printf("PWD:%s\n", getenv("PWD"));
  7 
  8     printf("MYENV:%s\n", getenv("MYENV"));                                                         
  9     printf("我是另一个c程序\n");
 10     printf("我是另一个c程序\n");
 11     printf("我是另一个c程序\n");
 12     printf("我是另一个c程序\n");
 13     printf("我是另一个c程序\n");
 14     return 0;
 15 }

myexec.c

    1 #include<stdio.h>
    2 #include<unistd.h>
    3 #include<stdlib.h>
    4 #include<assert.h>
    5 #include<sys/types.h>
    6 #include<sys/wait.h>
    7 int main()
    8 {
    9     printf("process is running...\n");
   10     pid_t id = fork();
   11     assert(id != -1);
   12 
   13     if(id == 0)
   14     {
   15         //这里的替换不会影响父进程,因为进程具有独立性
   16         sleep(1);
   17         //execl("/usr/bin/ls","ls","-a","-l","--color=auto",NULL);
   18         //
   19         //execlp("ls","ls","-a","-l","--color=auto",NULL);
   20 
   21         //char*const argv[] = {"ls", "-a", "-l", "--color=auto", NULL};
   22         //execv("/usr/bin/ls", argv);
   23         //execvp("ls", argv);
W> 24         char*const envp_[] = {(char*)"MYENV=111112223", NULL};
   25         extern char**environ;                                                                    
   26         //execle("./mybin", "mybin", NULL, envp_);//自定义环境变量
   27         putenv((char*)"MYENV=1122345");//将指定环境变量导入到系统统环境变量中
   28         execle("./mybin", "mybin", NULL, environ);//系统环境变量
   29         exit(1);
   30     }
   31     int status = 0;
   32     pid_t ret = waitpid(id, &status, 0);
   33     if(ret > 0)
   34     {
   35         printf("wait success:exit code:%d, sig number:%d\n", (status >> 8) & 0xff, status & 0x7f)      ;  
   36     }
   37     return 0;
   38}

结果:
在这里插入图片描述

4. int execv(const char *path, char *const argv[]);

示例:

    1 #include<stdio.h>
    2 #include<unistd.h>
    3 #include<stdlib.h>
    4 #include<assert.h>
    5 #include<sys/types.h>
    6 #include<sys/wait.h>
    7 int main()
    8 {
    9     printf("process is running...\n");
   10     pid_t id = fork();
   11     assert(id != -1);
   12 
   13     if(id == 0)
   14     {
   15         //这里的替换不会影响父进程,因为进程具有独立性
   16         sleep(1);
   17         //execl("/usr/bin/ls","ls","-a","-l","--color=auto",NULL);
   18         //
   19         //execlp("ls","ls","-a","-l","--color=auto",NULL);
   20 
W> 21         char*const argv[] = {"ls", "-a", "-l", "--color=auto", NULL};
   22         execv("/usr/bin/ls", argv);                                                              
   23         exit(1);
   24     }
   25     int status = 0;
   26     pid_t ret = waitpid(id, &status, 0);
   27     if(ret > 0)
   28     {
   29         printf("wait success:exit code:%d, sig number:%d\n", (status >> 8) & 0xff, status & 0x7f)      ;  
   30     }
   31 return 0;
   32 }

结果:
在这里插入图片描述

5. int execvp(const char *file, char *const argv[]);

示例:

    1 #include<stdio.h>
    2 #include<unistd.h>
    3 #include<stdlib.h>
    4 #include<assert.h>
    5 #include<sys/types.h>
    6 #include<sys/wait.h>
    7 int main()
    8 {
    9     printf("process is running...\n");
   10     pid_t id = fork();
   11     assert(id != -1);
   12 
   13     if(id == 0)
   14     {
   15         //这里的替换不会影响父进程,因为进程具有独立性
   16         sleep(1);
   17         //execl("/usr/bin/ls","ls","-a","-l","--color=auto",NULL);
   18         //
   19         //execlp("ls","ls","-a","-l","--color=auto",NULL);
   20 
W> 21         char*const argv[] = {"ls", "-a", "-l", "--color=auto", NULL};
   22         //execv("/usr/bin/ls", argv);
   23         execvp("ls", argv);                                                                      
   24         exit(1);                                                                      
   25     }                                                                                 
   26     int status = 0;                     
   27     pid_t ret = waitpid(id, &status, 0);
   28     if(ret > 0)                         
   29     {                                   
   30         printf("wait success:exit code:%d, sig number:%d\n", (status >> 8) & 0xff, status & 0x7f);                                       
   31     }
   32 return 0;
   33 }

结果:
在这里插入图片描述
execl还能执行其他文件的代码
示例代码:
mybin.c

  1 #include<stdio.h>
  2 int main()
  3 {
  4     printf("hello bit!!!\n");
  5     printf("hello bit!!!\n");
  6     printf("hello bit!!!\n");
  7     printf("hello bit!!!\n");                                                                      
  8     return 0;
  9 }

myexec.c

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 #include<assert.h>
  5 #include<sys/types.h>
  6 #include<sys/wait.h>
  7 int main()
  8 {
  9     printf("process is running...\n");
 10     pid_t id = fork();
 11     assert(id != -1);
 12 
 13     if(id == 0)
 14     {
 15         //这里的替换不会影响父进程,因为进程具有独立性
 16         sleep(1);
 17         //execl("/usr/bin/ls","ls","-a","-l","--color=auto",NULL);
 18         //
 19         //execlp("ls","ls","-a","-l","--color=auto",NULL);
 20 
 21         //char*const argv[] = {"ls", "-a", "-l", "--color=auto", NULL};
 22         //execv("/usr/bin/ls", argv);
 23         //execvp("ls", argv);
 24         execl("./mybin", "mybin", NULL);                                                           
 25         exit(1);
 26     }
 27     int status = 0;
 28     pid_t ret = waitpid(id, &status, 0);
 29     if(ret > 0)
 30     {
 31         printf("wait success:exit code:%d, sig number:%d\n", (status >> 8) & 0xff, status & 0x7f);
 32     }
 33     return 0;
 34 }

输出结果:
在这里插入图片描述

4.3 函数解释

  • 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。因为成功了,就和接下来的代码无关了,判断毫无意义。
  • 如果调用出错则返回-1
  • 所以exec函数只有出错的返回值而没有成功的返回值。

4.4 命名理解

  • l(list) : 表示参数采用列表,将参数一个一个的传入exec*
  • v(vector) : 参数放入数组中,统一传递,而不用进行使用可变参数方案
  • p(path) : 有p自动搜索环境变量PATH(如何找到程序的功能),不用告诉我程序的路径,你只要告诉我是谁,我会自动在环境变量PATH,进行可执行程序的查找。
  • e(env) : 表示自己维护环境变量
    在这里插入图片描述

在这里插入图片描述
exec调用举例如下:

#include <unistd.h>
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节。这些函数之间的关系如下图所示.
下图exec函数族 一个完整的例子:
在这里插入图片描述

五、我们可以综合前面的知识,做一个简易的shell

考虑下面这个与shell典型的互动:

[root@localhost epoll]# ls
client.cpp readme.md server.cpp utility.h
[root@localhost epoll]# ps
PID TTY TIME CMD
3451 pts/0 00:00:00 bash
3514 pts/0 00:00:00 ps

用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表,它随着时间的流逝从左向右移动。shell从用户读入字符串"ls"。shell建立一个新的进程,然后在那个进程中运行ls程序并等待那个进程结束。

然后shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序 并等待这个进程结束。
所以要写一个shell,需要循环以下过程:

  1. 获取命令行
  2. 解析命令行
  3. 建立一个子进程(fork)
  4. 替换子进程(execvp)
  5. 父进程等待子进程退出(wait)
    根据这些思路,和我们前面的学的技术,就可以自己来实现一个shell了。
    实现代码:
    myshell.c
    1 #include<stdio.h>
    2 #include<stdlib.h>
    3 #include<unistd.h>
    4 #include<sys/types.h>
    5 #include<sys/wait.h>
    6 #include<string.h>
    7 #include<assert.h>
    8 
    9 #define NUM 1024
   10 #define OPT_NUM 64
   11 
   12 char lineCommand[NUM];
   13 char *myargv[OPT_NUM];
   14 int lastCode = 0;
   15 int lastSig = 0;
   16 int main()
   17 {
   18     while(1)
   19     {
   20         //输出提示符
   21         printf("[sk@alcloud ~] ");
   22         fflush(stdout);
   23 
   24         //获取用户输入
   25         char*s = fgets(lineCommand, sizeof(lineCommand) - 1, stdin);
   26         assert(s != NULL);
   27         (void)s;
   28         //清除最后一个\n
   29         lineCommand[strlen(lineCommand) - 1] = 0;
   30         //字符串切割
   31         myargv[0] = strtok(lineCommand, " ");
   32         int i = 1;
   33         if(myargv[0] != NULL && strcmp(myargv[0], "ls") == 0)                                    
   34         {
   35             myargv[i++] = (char*)"--color=auto";
   36         }
W> 37         while(myargv[i++] = strtok(NULL, " "));
   38         //如果是cd命令,不需要创建子进程,让shell自己执行对应的命令,本质就是执行系统接口
   39         if(myargv[0] != NULL && strcmp(myargv[0], "cd") == 0)
   40         {
   41             if(myargv[1] != NULL)
   42             {
   43                 chdir(myargv[1]);
   44             }
   45             continue;
   46         }
   47         if(myargv[0] != NULL && myargv[1] != NULL && strcmp(myargv[0], "echo") == 0)
   48         {
   49             if(strcmp(myargv[1], "$?") == 0)
   50             {
   51                 printf("%d, %d\n", lastCode, lastSig);
   52             }
   53             else
   54             {                                                                                    
   55                 printf("%s\n", myargv[1]);
   56             }
   57             continue;
   58         }
   59         //测试是否成功
   60         //for(int i = 0; myargv[i]; i++)
   61         //{
   62         //    printf("%d : %s\n", i, myargv[i]);
   63         //}
   64         
   65         //执行命令
   66         pid_t id = fork();
   67         assert(id != -1);
   68 
   69         if(id == 0)
   70         {
   71             execvp(myargv[0], myargv);
   72             exit(1);
   73         }
   74         int status = 0;
W> 75         pid_t ret =  waitpid(id, &status, 0);
   76         assert(ret != -1);
   77         lastCode = (status >> 8) & 0xff;
   78         lastSig = status & 0x7f;
   79     }
   80     return 0;
   81 }

在同学们实现的shell中可能遇到过这个问题,就是我们在使用cd命令的时候没有用,其实并不是没有用,那是因为在这个子进程的工作目录中执行的命令并不会影响父进程,执行完之后就没了,继续用的是父进程,就是shell,但是我们可以使用chdir(const char* path)命令更改子进程命令的工作目录。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值