• 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("[%s@%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);  
            }      
        }  
    }  
    
    
    • 运行结果

  • 相关阅读:
    输入n个整数,输出其中最小的k个
    输出单向链表中倒数第k个结点
    扑克牌大小
    Optional<T> 避免和null检查相关的bug
    筛选、切片、匹配、查找、匹配、归约
    java8 流 中的常用函数式接口
    action 和 controller 单例与多例问题
    Collections.sort Comparator.comparing 冒泡排序 效率对比
    java8 流
    将逗号分割的列,变成多列
  • 原文地址:https://www.cnblogs.com/20165324hcj/p/10012854.html
Copyright © 2020-2023  润新知