记录一下编写linux内核课程中遇到的问题和解决过程。

一、程序出不去?

 先看程序

// myshell.c
#include <stdio.h>
// used by exit
#include <stdlib.h>
// used by func fork,execlp,wait
#include <sys/types.h>
#include <unistd.h>
#include <wait.h>
#include <string.h>

int main(){
    // 记录子进程结束状态
    int status_c;
    while (1)
    {
        pid_t pid_c, pid_cid;
        // 创建子进程
	    pid_c = fork();
        // 判断fork调用结果
        // 出错
        if (pid_c == -1)
        {
            printf("Error when fork called\n");
            exit(-1);
        }
        // 子进程 处理命令行传入参数
        else if (pid_c == 0)
        {   
            // 接受命令行传入参数,存入字符串
            char cmd_get[20];
            // 模拟当前用户
            printf("h_zeng@ubuntu:");
            // 读取传入命令
            scanf("%s", cmd_get);
            if (strcmp(cmd_get, "exit") == 0)
            {
                printf("exit my shell\n");
                exit(1);
            }
            //printf("pid when child process : %d\n", getpid());
            // 处理echo命令
            if (strcmp(cmd_get, "echo") == 0)
            {
                scanf("%s", cmd_get);
                // execl执行失败,返回
                if(execl("/bin/echo","echo",cmd_get,NULL) == -1)
                {
                    printf("Error when execl called\n");
				    return -1;
                }
                exit(0);
            }
            // 其余情况自动从环境变量“$PATH”所指出的路径中进行查找
            // execlp执行失败,返回
            else if (execlp(cmd_get, cmd_get, NULL) == -1) 
            {
                //printf("cmd_get : %s\n", cmd_get);
                printf("Error when execlp called\n");
				exit(-1);
			}
            exit(0);
        }
        // 父进程 等待子进程结束
        else
        {   
            // 等待子程序返回
            pid_cid = wait(&status_c);
            //printf("pid when wait : %d\n", getpid());
            // wait调用失败
            if(pid_cid == -1)
            {
                printf("Error when wait called\n");
                break;
            }
            // wait调用成功
            // 子程序非正常结束返回,结束shell 
            //printf("pid from wait(aka ended child process pid) : %d\n", pid_cid);
            //printf("status_c : %d\n", WEXITSTATUS(status_c));
            if(status_c < 0) 
            {
                printf("Exception occurs when child process end\n");
                break;
            }
        }
    }
    return 0;
}

 编译运行,得到如图结果,普通的命令包括echo等都能正常完成,但是到exit的时候程序执行完了可以看到开头仍是我程序里写的模拟用户。
程序出不去
 回到程序,分析一波,emmm,啥都没分析出来,参考网上的类似程序实现,发现他把第一个命令行传入参数的接收操作放在了fork之前。嗯,在产生子程序之前判断了读入exit,然后结束整个程序,应该可行。改代码!

二、多出来的子程序?

// myshell.c
int main(){
    // 记录子进程结束状态
    int status_c;
    while (1)
    {
        pid_t pid_c, pid_cid;
        // 接受命令行传入参数,存入字符串
        char cmd_get[20];
        // 模拟当前用户
        printf("h_zeng@ubuntu:");
        // 读取传入命令
        scanf("%s", cmd_get);
        if (strcmp(cmd_get, "exit") == 0)
        {
            printf("exit my shell\n");
            exit(1);
        }
        // 创建子进程
	    pid_c = fork();
        // 判断fork调用结果
        // 出错
        if (pid_c == -1)
        {
            printf("Error when fork called\n");
            exit(-1);
        }
        // 子进程 处理命令行传入参数
        else if (pid_c == 0)
        {   
            //printf("pid when child process : %d\n", getpid());
            // 处理echo命令
            if (strcmp(cmd_get, "echo") == 0)
            {
                scanf("%s", cmd_get);
                // execl执行失败,返回
                if(execl("/bin/echo","echo",cmd_get,NULL) == -1)
                {
                    printf("Error when execl called\n");
				    return -1;
                }
                exit(0);
            }
            // 其余情况自动从环境变量“$PATH”所指出的路径中进行查找
            // execlp执行失败,返回
            else if (execlp(cmd_get, cmd_get, NULL) == -1) 
            {
                //printf("cmd_get : %s\n", cmd_get);
                printf("Error when execlp called\n");
				exit(-1);
			}
            exit(0);
        }
        // 父进程 等待子进程结束
        else
        {   
            // 等待子程序返回
            pid_cid = wait(&status_c);
            //printf("pid when wait : %d\n", getpid());
            // wait调用失败
            if(pid_cid == -1)
            {
                printf("Error when wait called\n");
                break;
            }
            // wait调用成功
            // 子程序非正常结束返回,结束shell 
            //printf("pid from wait(aka ended child process pid) : %d\n", pid_cid);
            //printf("status_c : %d\n", WEXITSTATUS(status_c));
            if(status_c < 0) 
            {
                printf("Exception occurs when child process end\n");
                break;
            }
        }
    }
    return 0;
}

 再次编译运行,得到如图结果,exit能正常推出了,但是到echo的时候,正常打印出hi之后,execlp函数出现了错误,并且下面一行打印了两次模拟用户信息。
多出来的子程序
 回到程序,分析一波,很容易猜到程序比预想的“分裂”了多一次,导致把第二次读入的hi当成命令传入给execlp函数,但是突然发现一个问题,execlp函数出错后,子进程返回异常,父进程接收到异常应该结束掉整个shell程序,但是实际并没有。于是再次改代码,加上一些printf打印pid等信息看看猜想正确与否,以及为什么程序没有因为异常结束。

// myshell.c
int main(){
    // 记录子进程结束状态
    int status_c;
    while (1)
    {
        pid_t pid_c, pid_cid;
        // 接受命令行传入参数,存入字符串
        char cmd_get[20];
        // 模拟当前用户
        printf("h_zeng@ubuntu:");
        // 读取传入命令
        scanf("%s", cmd_get);
        if (strcmp(cmd_get, "exit") == 0)
        {
            printf("exit my shell\n");
            exit(1);
        }
        // 创建子进程
	    pid_c = fork();
        // 判断fork调用结果
        // 出错
        if (pid_c == -1)
        {
            printf("Error when fork called\n");
            exit(-1);
        }
        // 子进程 处理命令行传入参数
        else if (pid_c == 0)
        {   
            printf("pid when child process : %d\n", getpid());
            // 处理echo命令
            if (strcmp(cmd_get, "echo") == 0)
            {
                scanf("%s", cmd_get);
                // execl执行失败,返回
                if(execl("/bin/echo","echo",cmd_get,NULL) == -1)
                {
                    printf("Error when execl called\n");
				    return -1;
                }
                exit(0);
            }
            // 其余情况自动从环境变量“$PATH”所指出的路径中进行查找
            // execlp执行失败,返回
            else if (execlp(cmd_get, cmd_get, NULL) == -1) 
            {
                printf("cmd_get : %s\n", cmd_get);
                printf("Error when execlp called\n");
				exit(-1);
			}
            exit(0);
        }
        // 父进程 等待子进程结束
        else
        {   
            // 等待子程序返回
            pid_cid = wait(&status_c);
            printf("pid when wait : %d\n", getpid());
            // wait调用失败
            if(pid_cid == -1)
            {
                printf("Error when wait called\n");
                break;
            }
            // wait调用成功
            // 子程序非正常结束返回,结束shell 
            printf("pid from wait(aka ended child process pid) : %d\n", pid_cid);
            // printf("status_c : %d\n", WEXITSTATUS(status_c));
            printf("status_c : %d\n", status_c);
            if(status_c < 0) 
            {
                printf("Exception occurs when child process end\n");
                break;
            }
        }
    }
    return 0;
}

 编译运行,得到如图结果,可以看到确实在用户输入echo hi之后有两个子程序执行了,第一个子进程正常结束,pid被父进程的wait函数捕获,值也一致,第二个子进程接收到了错误了hi为命令行输入,抛出execlp函数错误。然后--------------!wait函数得到的子进程返回状态居然是65280!!
多出来的子程序

三、wait函数status

 好了,知道第一次为什么程序结束不了了,父进程在子进程异常结束后,wait函数得到的子进程结束状态并不是想当然的 -1。 随即查阅了wait函数status参数的意义,发现要用WEXITSTATUS等宏来解析。重新编写程序。

// myshell.c

int main(){
    // 记录子进程结束状态
    int status_c;
    while (1)
    {
        pid_t pid_c, pid_cid;
        // 创建子进程
	    pid_c = fork();
        // 判断fork调用结果
        // 出错
        if (pid_c == -1)
        {
            printf("Error when fork called\n");
            exit(-1);
        }
        // 子进程 处理命令行传入参数
        else if (pid_c == 0)
        {   
            printf("pid when child process : %d\n", getpid());
            // 接受命令行传入参数,存入字符串
            char cmd_get[20];
            // 模拟当前用户
            printf("h_zeng@ubuntu:");
            // 读取传入命令
            scanf("%s", cmd_get);
            if (strcmp(cmd_get, "exit") == 0)
            {
                printf("exit my shell\n");
                exit(1);
            }
            // 处理echo命令
            if (strcmp(cmd_get, "echo") == 0)
            {
                scanf("%s", cmd_get);
                // execl执行失败,返回
                if(execl("/bin/echo","echo",cmd_get,NULL) == -1)
                {
                    printf("Error when execl called\n");
				    return -1;
                }
                exit(0);
            }
            // 其余情况自动从环境变量“$PATH”所指出的路径中进行查找
            // execlp执行失败,返回
            else if (execlp(cmd_get, cmd_get, NULL) == -1) 
            {
                printf("cmd_get : %s\n", cmd_get);
                printf("Error when execlp called\n");
				exit(-1);
			}
            exit(0);
        }
        // 父进程 等待子进程结束
        else
        {   
            // 等待子程序返回
            pid_cid = wait(&status_c);
            printf("pid when wait : %d\n", getpid());
            // wait调用失败
            if(pid_cid == -1)
            {
                printf("Error when wait called\n");
                break;
            }
            // wait调用成功
            // 子程序非正常结束返回,结束shell 
            printf("pid from wait(aka ended child process pid) : %d\n", pid_cid);
            printf("status_c : %d\n", WEXITSTATUS(status_c));
            if(WEXITSTATUS(status_c) != 0 && WEXITSTATUS(status_c) != 1) 
            {
                printf("Exception occurs when child process end\n");
                break;
            }
            else if(WEXITSTATUS(status_c) == 1) 
            {
                printf("the shell ended cause you typed 'exit' \n");
                break;
            }
        }
    }
    return 0;
}

 编译运行,得到如图结果,程序可以正常运行,WEXITSTATUS宏可以将子进程结束状态与子程序exit中整型变量对应。

注意:如果exit(-1),那么解析出来的是255,但是其他机器上我不知道,保险起见把除0/1以外的返回视为异常。

多出来的子程序

四、总结

 程序整体思路是利用fork产生子进程,处理命令行输入参数的处理,然后结束子进程,父进程得到子进程返回状态,判断整体程序是否应该继续执行