20165324_mybash的实现

Posted 20165324hcj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了20165324_mybash的实现相关的知识,希望对你有一定的参考价值。

20165324_mybash的实现

实验要求

  • 实验要求:
    1. 使用fork,exec,wait实现mybash
    2. 写出伪代码,产品代码和测试代码
    3. 发表知识理解,实现过程和问题解决的博客(包含代码托管链接)

背景知识

Shell的介绍

  • shell是一个程序,可以称之为壳程序,用于用户与操作系统进行交互。用来区别与核,相当于是一个命令解析器,Shell有很多中,这里列出其中几种
    1. Bourne SHell(sh)
    2. Bourne Again SHell(bash)
    3. C SHell(csh)
    4. KornSHell(ksh)
    5. zsh
  • 各个shell的功能都差不太多,在某些语法的下达下面有些区别,Linux预设就是bash。这里主要介绍bash

bash

  • bash命令是sh命令的超集大多数sh脚本都可以在bash下运行,bash主要有如下这些功能:
    1. 记录历史命令:bash可以记录曾经的命令,保持在~/.bash_history文件中,只保存上次注销登录之后的命令。
    2. tab键自动补全:使用tab见可以自动不全命令或者目录。
    3. alias命令别名:可以使用alias ll=‘ls -al‘来设置命令的别名。
    4. 工作控制:可以将某些任务放在后台去运行,这里不多种介绍。
    5. 程序脚本:可以执行shell脚本文件。
    6. 通配符:在查找相关文件或者执行相关命令时,可以使用通配符*。
    7. 内建命令type:可以使用type命令来查看某个命令是否为内建在bash当中的命令。

      进程

技术分享图片

  • 进程的状态:
    1. 创建(created)
    2. 执行(running)
    3. 就绪(ready)
    4. 阻塞(blocked)
    5. 终止(terminated)

      bash示例和书写流程

  • 命令行输入:gedit hello_shell.sh
  • 编辑器内输入:
#!/bin/bash

for ((i=0; i<10; i++));do
   echo "hello shell"
done

exit 0
  • 为文件添加可执行权限:$ chmod 755 hello_shell.sh
  • 执行脚本:$ ./hello_shell.sh
  • 运行截图:

技术分享图片

  • 说明:
    1. #!/bin/bash:它是bash文件声明语句,表示是以/bin/bash程序执行该文件。它必须写在文件的第一行。
    2. echo "hello bash" : 表示在终端输出“hello bash”。
    3. exit 0 : 表示返回0。在bash中,0表示执行成功,其他表示失败。
  • 执行bash脚本$ ./bash,在终端输出“hello shell”。

fork

  • fork:
    1. 指一个进程,包括代码、数据和分配给进程的资。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
    2. 一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。

技术分享图片

  • 测试代码Testfork.c
#include <unistd.h>  
#include <stdio.h>   
int main ()   
{   
    pid_t fpid; //fpid表示fork函数返回的值  
    int count=0;  
    fpid=fork();   
    if (fpid < 0)   
        printf("error in fork!");   
    else if (fpid == 0) {  
        printf("i am the child process, my process id is %d
",getpid());   
        printf("I‘m the child
");
        count++;  
    }  
    else {  
        printf("i am the parent process, my process id is %d
",getpid());   
        printf("I‘m the father
");  
        count++;  
    }  
    printf("统计结果是: %d
",count);  
    return 0;  
}  
  • 运行结果

技术分享图片

  • 代码分析: 一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。 执行完fork后,进程1的变量为count=0,fpid!=0(父进程)。进程2的变量为count=0,fpid=0(子进程),这两个进程的变量都是独立的,存在不同的地址中,不是共用的,这点要注意。可以说,我们就是通过fpid来识别和操作父子进程的。

wait

  • wait:由于fork产生的父子进程的执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略,所以要实现mybash必须使用wait函数。如果在使用fork()之前调用wait(),wait()的返回值则为-1,正常情况下wait()的返回值为子进程的PID.

技术分享图片

  • wait的函数原型是:
  #include <sys/types.h> /* 提供类型pid_t的定义 */
  #include <sys/wait.h>
  pid_t wait(int *status);
  • 返回值: 如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno中。
    1. 进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经 退出,如果让它找到了这样一个已经变成僵尸的子进程, wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止
    2. 参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉的毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,就象下面这样:pid = wait(NULL);
    3. 如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。

下面运行一个例子来理解wait调用:

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
 
int main()
{
    pid_t pc, pr;
    pc = fork();
    if ( pc < 0 ) /* 如果出错 */
    {
        printf("create child prcocess error: %s/n", strerror(errno));
        exit(1);
    }
    else if ( pc == 0) /* 如果是子进程 */
    {
        printf("I am child process with pid %d 
", getpid());
        sleep(3);/* 睡眠3秒钟 */
        exit(0);
    }
    else /* 如果是父进程 */
    {
        printf("Now in parent process, pid = %d/n", getpid());
        printf("I am waiting child process to exit.
");
        pr = wait(NULL); /* 在这里等待子进程结束 */
        if ( pr > 0 ) /*子进程正常返回*/
        printf("I catched a child process with pid of %d
", pr);
        else /*出错*/
        printf("error: %s/n.
", strerror(errno));
    }
    exit(0);
}
  • 运行结果:

技术分享图片

  • 代码分析:设定的让子进程睡眠3s的时间,只有子进程从睡眠中苏醒过来,它才能正常退出,也就才能被父进程捕捉到。这里不管设定子进程睡眠的时间有多长,父进程都会一直等待下去。

exec

  • exec:exec命令用于调用并执行指令的命令。exec命令通常用在shell脚本程序中,可以调用其他的命令。如果在当前终端中使用命令,则当指定的命令执行完毕后会立即退出终端。
  • man -k exec查看手册得:

技术分享图片

  • 语法格式:
    1. int execl(const char pathname, const char arg0, ... /* (char )0 );,execl()函数用来执行参数path字符串所指向的程序,第二个及以后的参数代表执行文件时传递的参数列表,最后一个参数必须是空指针以标志参数列表为空.
    2. int execv(const char path, char const argv[]);,execv()函数函数用来执行参数path字符串所指向的程序,第二个为数组指针维护的程序参数列表,该数组的最后一个成员必须是空指针。
    3. int execlp(const char filename, const char arg0, ... /* (char )0 / );,execlp()函数会从PATH环境变量所指的目录中查找文件名为第一个参数指示的字符串,找到后执行该文件,第二个及以后的参数代表执行文件时传递的参数列表,最后一个参数必须是空指针.
    4. int execvp(const char file, char const argv[]);,execvp()函数会从PATH环境变量所指的目录中查找文件名为第一个参数指示的字符串,找到后执行该文件,第二个及以后的参数代表执行文件时传递的参数列表,最后一个成员必须是空指针。
  • 由于exec函数会取代执行它的进程,一旦exec函数执行成功, 它就不会返回了,进程结束。但是如果exec函数执行失败, 它会返回失败的信息, 而且进程继续执行后面的代码。所以通常exec会放在fork() 函数的子进程部分,来替代子进程执行,执行成功后子程序就会消失, 但是执行失败的话,必须用exit()函数来让子进程退出。

    mybash

  • mybsah的实现代码:

#include <stdio.h>  
#include <stdlib.h>  
#include <assert.h>  
#include <string.h>  
#include <unistd.h>  
#include <pwd.h>  
#define PATH "/home/"   
  
void print_info()  
{  
    uid_t id = getuid();  
    char *status = "$";  
    if( id == 0)  
    {  
        status = "#";  
    }  
    struct passwd *p = getpwuid(id);  
    if(p == NULL)  
    {  
        printf("mybash>>");  
        fflush(stdout);  
        return;  
    }  
      
    char path[128] = {0};  
    getcwd(path,256);  
      
    char *q = "/";  
    char *s = strtok(path,"/");  
    while( s != NULL)  
    {  
        q = s;  
        s = strtok(NULL,"/");  
    }  
    char hostname[128] = {0};  
    gethostname(hostname,128);  
    printf("[%[email protected]%s %s]%s ",p->pw_name,hostname,q,status);  
}  
int main()  
{  
    while(1)  
    {  
        print_info();  
        char buff[128] = {0};  
        fgets(buff,128,stdin);  
        buff[strlen(buff)-1] = 0;  
        char *myargv[10] = {0};  
        char *s = strtok(buff," ");  
        if(s == NULL)  
        {  
            continue;  
        }  
        myargv[0] = s;  
        int i = 1;  
        while((s = strtok(NULL," ")) != NULL)  
        {  
            myargv[i++] = s;  
        }  
        if(strcmp(myargv[0],"exit") == 0)  
        {  
            exit(0);  
        }     
        else if(strcmp(myargv[0],"cd") == 0)  
        {  
            if(chdir(myargv[1]) == -1)  
            {  
                perror("chdir error");  
            }  
            continue;  
        }  
        pid_t pid = fork();  
        assert(pid != -1);  
        if(pid == 0)  
        {   
            char path[256] = {0};  
            if(strncmp(myargv[0],"./",2) != 0 && strncmp(myargv[0],"/",1) != 0)  
            {  
                strcpy(path,PATH);  
            }  
            strcat(path,myargv[0]);  
            execv(path,myargv);  
            perror("execv error");  
            exit(0);  
        }  
        else  
        {  
            wait(NULL);  
        }      
    }  
}  
  • 运行结果

技术分享图片

以上是关于20165324_mybash的实现的主要内容,如果未能解决你的问题,请参考以下文章

实现mybash

mybash的实现

20165322 第七周 mybash 的实现

20165223 《信息安全系统设计基础》 实现mybash

mybash的实现

20165220 mybash