操作系统lab1-shell

内容与设计思想

实现一个基本的Shell,能够实现以下功能:

​ 1. 带参数的程序运行功能。

​ 2. 重定向功能,将文件作为程序的输入/输出。

​ 3. 管道符号“|”,在程序间传递数据。

​ 4. 后台符号& ,表示此命令将以后台运行的方式执行。

​ 5. 工作路径移动命令cd。

​ 6. 程序运行统计mytop。

​ 7. shell退出命令exit。

​ 8. history n显示最近执行的n条指令

实验过程

常用函数封装

封装了带有错误处理的open(),fopen(),execvp()函数,make64()函数合并高低周期,sleep_ms()通过select()函数实现毫秒级的sleep()

/*
 * 合并高低周期
 */
static inline u64_t make64(unsigned long lo, unsigned long hi) {
    return ((u64_t)hi << 32 | (u64_t)lo);
}

/*
 * 实现毫秒级的sleep
 */
static void sleep_ms(unsigned int secs) {
    struct timeval tval;
    tval.tv_sec=secs/1000;
    tval.tv_usec=(secs*1000)%1000000;
    select(0,NULL,NULL,NULL,&tval);
}

/*
 * 封装带有错误处理的open函数
 */
int Open(char *path, int param) {
    int file_fd;
    if ((file_fd = open(path, param)) < 0) {
        fprintf(stderr, "OPEN FILE ERROR.\n");
        exit(1);
    } else return file_fd;

}
/*
 * 封装带有错误处理的fopen函数
 */
FILE* Fopen(char *path, char *mode) {
    FILE* fp;
    if ((fp = fopen(path, mode)) == NULL) {
        fprintf(stderr, "FOPEN ERROR.\n");
        exit(1);
    }
    return fp;
}
/*
 * 封装带有错误处理的execvp函数
 */
void Execvp(char *path, char **params) {
    if (execvp(path, params) < 0) {
        fprintf(stderr, "EXECVP ERROR.\n");
        exit(1);
    }
}

命令行预处理和解析

preparse()函数在重定向符号前后添加一个空格,便于后续解析。

/*
 * 在重定向前后加空格,便于后续解析
 */
void preparse(char *cmdline) {
    char newcmdline[MAXLEN];
    int len = strlen(cmdline), j = 0;
    for (int i = 0; i < len; i++) {
        if (cmdline[i] == '<') newcmdline[j++] = ' ', newcmdline[j++] = '<', newcmdline[j++] = ' '; // '<'
        else if (cmdline[i] == '>') { 
            if (cmdline[i + 1] == '>') { // '>>'
                newcmdline[j++] = ' ';
                newcmdline[j++] = '>';
                newcmdline[j++] = '>';
                newcmdline[j++] = ' ';
                i++;
            } else { // '>'
                newcmdline[j++] = ' ';
                newcmdline[j++] = '>';
                newcmdline[j++] = ' ';
            }
        } else newcmdline[j++] = cmdline[i];
    }
    newcmdline[j] = '\0';
    strcpy(cmdline, newcmdline);
}

parseline()函数将一行字符串命令解析为多个参数,存放在argv中,并返回argc

/*
 * 解析参数,把参数存入argv,并返回argc
 */
int parseline(const char *cmdline, char **argv) {
    int len = strlen(cmdline), argc = 0, temp = 0;
    for (int i = 0; i < len; i++) {
        if (cmdline[i] == ' ') {
            if (i != 0 && cmdline[i - 1] != ' ') {
                argv[argc][temp] = '\0';
                argc++;
                temp = 0;
            }
            continue;
        }
        if (!temp) argv[argc] = (char *)malloc(sizeof(char) * MAXLEN);
        argv[argc][temp++] = cmdline[i];
    }
    if (temp == 0) argc--;
    return argc + 1;
}

带参数的程序运行功能,在最后加&符号,表示此命令后台执行

先利用fork()创建子进程,在子进程中通过execvp()函数执行程序,父进程通过waitpid挂起,等待子进程结束后继续运行。

如果检测到后台执行&符号,则将子进程的标准输入、输出、错误流重定向到/dev/null,并通过signal(SIGCHLD, SIG_IGN)使得minix接管该进程,即不产生僵死进程。

pid_t pid;
if ((pid = fork()) == 0) {
    if (!wait) {
        direct_null();
        signal(SIGCHLD, SIG_IGN); // 使得minix接管该进程,即不产生僵死进程
    } else redirect(argv, &argc);
    Execvp(argv[0], argv);
}
int status;
if (wait) waitpid(pid, &status, 0);

重定向功能,将文件作为程序的输入/输出

解析参数,检查是否存在<,>,>>,若有则利用dup()进行重定向

<表示文件写入,重定向输入流,文件打开方式为O_RDONLY,即只读

>表示覆盖写,重定向输出流,文件打开方式为O_WRONLY | O_CREAT | O_TRUNC,即只写,若文件不存在则创建新文件,清空原文件

>>表示追加写,重定向输出流,文件打开方式为O_WRONLY | O_CREAT | O_APPEND,即只写,若文件不存在则创建新文件,在原文件后追加

/*
 * 根据>, >>, <重定向标准输入输出
 */
void redirect(char **argv, int *argc) {
    for (int i = 0; i < *argc; i++) {
        if (strcmp(argv[i], "<") == 0) {
            if (i + 1 == *argc) {
                fprintf(stderr, "REDIRECT ERROR.\n");
                exit(0);
            }
            close(STDIN_FILENO);
            int file_fd = Open(argv[i + 1], O_RDONLY);

            dup(file_fd);
            *argc -= 2; // 去掉这两个参数 并把后面的参数都往前移两位
            for (int j = i; j < *argc; j++) strcpy(argv[j], argv[j + 2]);
        }
        if (strcmp(argv[i], ">") == 0) {
            if (i + 1 == *argc) {
                fprintf(stderr, "REDIRECT ERROR.\n");
                exit(0);
            }
            close(STDOUT_FILENO);
            int file_fd = Open(argv[i + 1], O_WRONLY | O_CREAT | O_TRUNC);
            dup(file_fd);
            *argc -= 2;
            for (int j = i; j < *argc; j++) strcpy(argv[j], argv[j + 2]);
        }
        if (strcmp(argv[i], ">>") == 0) {
            if (i + 1 == *argc) {
                fprintf(stderr, "REDIRECT ERROR.\n");
                exit(0);
            }
            close(STDOUT_FILENO);
            int file_fd = Open(argv[i + 1], O_WRONLY | O_CREAT | O_APPEND);
            dup(file_fd);
            *argc -= 2;
            for (int j = i; j < *argc; j++) strcpy(argv[j], argv[j + 2]);
        }
    }
    argv[*argc] = NULL; // 保证argv以NULL为结尾
}

管道符号“|”,在程序间传递数据。

利用pipe()和dup()实现管道。根据”|”的位置,将一行命令分割为前后两部分,前一部分由重定向输出后的子进程执行,后一部分由重定向输入后的父进程执行。多管道的情况也可以正常递归处理。

/*
 * 子进程执行cmdline1,将输出通过管道作为主进程cmdline2的输入
 */
void pipeline(char *cmdline1, char *cmdline2) {
    int fd[2];
    pipe(&fd[0]);
    if (fork() == 0) { // 子进程,重定向输出
        close(fd[0]);
        close(STDOUT_FILENO);
        dup(fd[1]);
        close(fd[1]);
        eval(cmdline1, 1);
        exit(0);
    } else { // 父进程,重定向输入
        close(fd[1]);
        close(STDIN_FILENO);
        dup(fd[0]);
        close(fd[0]);
        eval(cmdline2, 1);
    }
}

工作路径移动命令cd。

利用chdir()函数实现

if (!strcmp(argv[0], "cd")) {
    chdir(argv[1]);
    return 1;
}

程序运行统计mytop

/*
 * 统计内存和cpu使用情况
 */
void mytop() {  
    int totSize, freeSize, cachedSize, total_proc, proc_num_prev = 0, proc_num_now = 0;
    u64_t total_ticks_pre = 0, total_ticks_now = 0, idle_ticks_pre = 0, idle_ticks_now = 0;
    get_meminfo(&totSize, &freeSize, &cachedSize);
    printf("main memory: ");
    printf(" %dK total,", totSize);
    printf(" %dK free,", freeSize);
    printf(" %dK cached\n", cachedSize);
    get_kinfo(&total_proc);
    printf("Total procs: %d\n", total_proc);

    get_pidinfo(prev, &proc_num_prev, &total_ticks_pre, &idle_ticks_pre);
    sleep_ms(500);
    get_pidinfo(now, &proc_num_now, &total_ticks_now, &idle_ticks_now);
    double CPU_util = 100 - (double)100 * (idle_ticks_now - idle_ticks_pre) / (total_ticks_now - total_ticks_pre);
    printf("CPU utilization : %.4f%%\n", CPU_util);
}

读取/proc/meminfo获取内存使用情况

/*
 * 读取/proc/meminfo
 */
void get_meminfo(int *totSize, int *freeSize, int *cachedSize) {
    FILE *fp;
    fp = Fopen("/proc/meminfo", "r");
    int pagesize, total, free, largest, cached;
    fscanf(fp, "%d %d %d %d %d", &pagesize, &total, &free, &largest, &cached);
    *totSize = pagesize * total / 1024;
    *freeSize = pagesize * free / 1024;
    *cachedSize = pagesize * cached / 1024;
    fclose(fp);
}

读取/proc/kinfo获取进程总数

/*
 * 读取/proc/kinfo
 */
void get_kinfo(int *total_proc) {
    FILE *fp;
    fp = Fopen("/proc/kinfo", "r");
    int proc, task;
    fscanf(fp, "%d %d", &proc, &task);
    *total_proc = proc + task;
    fclose(fp);
}

读取/proc/pid/psinfo获取每个进程的cpu占用时间

/*
 * 读取/proc下所有文件夹
 */
void get_pidinfo(struct proc *p, int *num, u64_t *total, u64_t *idle) {
    DIR *p_dir;
    char *end;
    if ((p_dir = opendir("/proc/")) == NULL) {
        fprintf(stderr, "OPENDIR ERROR.\n");
        exit(1);
    }
    struct dirent *p_ent;
    *num = 0;
    for (p_ent = readdir(p_dir); p_ent != NULL; p_ent = readdir(p_dir)) {
        int pid = strtol(p_ent->d_name, &end, 10);
        if (!pid) continue;
        (*num)++;
        parse_file(pid, &p[*num], total, idle);
    }
    closedir(p_dir);
}
/*
 * 读取/proc/pid/psinfo
 */
void parse_file(int pid, struct proc *p, u64_t *total, u64_t *idle) {
    char path[MAXPATH];
    sprintf(path, "/proc/%d/psinfo", pid);
    FILE *fp = Fopen(path, "r");
    fscanf(fp, "%d", &(p->version));
    fscanf(fp, " %c", &(p->type));
    fscanf(fp, " %d", &(p->endpoint));
    fscanf(fp, " %s", p->name);
    fscanf(fp, " %c", &(p->state));
    fscanf(fp, " %d", &(p->blocked));
    fscanf(fp, " %d", &(p->priority));
    fscanf(fp, " %d", &(p->user_time));
    fscanf(fp, " %d", &(p->ticks));
    fscanf(fp, " %lu", &(p->cycle_high));
    fscanf(fp, " %lu", &(p->cycle_low));
    fscanf(fp, " %llu", &(p->memory));
    fscanf(fp, " %lu %lu", &(p->effuid), &(p->nice));
    p->cpucycles[0] = make64(p->cycle_low, p->cycle_high);
    *total += p->cpucycles[0];
    if (p->endpoint == -4) 
        *idle = p->cpucycles[0];
    // printf("PID : %d  CYCLE : %llu  low : %lu  high : %lu  ticks : %d  %c  %c\n", pid, p->cpucycles[0], p->cycle_low, p->cycle_high, p->ticks, p->type, p->state);
    fclose(fp);
}

其中psinfo中的每个参数对应的含义如下:

img

为了计算cpu占用率,需要连续两次读取cpu使用情况并计算差值,这里选择间隔为500ms,利用select()函数实现。通过所有进程cpu占用时间累加获得total_ticks,通过/proc/-4/psinfo获得空闲的idle_ticks,cpu占用率为(1-idle_ticks/total_ticks)*100%

shell退出命令exit。

利用exit(0)实现退出

if (!strcmp(argv[0], "exit")) {
    exit(0);
}

history n显示最近执行的n条指令

利用全局变量char *record[MAXHISTORY]记录最近执行的指令,rec_size记录指令条数。

if (!strcmp(argv[0], "history")) {
    redirect(argv, &argc);
    if (argc == 1) {
        for (int i = 0; i <= rec_size; i++) {
            printf("  %d  %s", i + 1, record[i]);
        }
    } else {
        int number;
        // fprintf(stderr, "%s\n", argv[1]);
        sscanf(argv[1], "%d", &number);
        for (int i = MAX(0, rec_size - number + 1); i <= rec_size; i++) {
            printf("  %d  %s", i + 1, record[i]);    
        }
    }
    return 1;
}

编译和测试

img

img

img

img

img

img

img

img

img

img

总结

实现了一个基本的shell,包含重定向和管道功能。实现过程中对一些常用的函数以及系统调用进行了错误处理的封装。实现mytop()时参考了MINIX原生top命令的实现,一些系统调用的使用方法参考了《Unix环境高级编程》第3和第8节。

完整代码:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <sys/wait.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>

typedef unsigned long long u64_t;

#define debug(x) fprintf(stderr, "%d ", x)
#define debugl(x) fprintf(stderr, "%d\n", x)

#define MAXLINE 1024
#define MAXLEN 32
#define MAXARGS 128
#define MAXPATH 128
#define MAXHISTORY 1000
#define PROC_NAME_LEN 16
#define PROC_MAX 1024

#define MAX(a, b) (a) > (b) ? (a) : (b)

/*
 * 记录进程信息
 */ 
struct proc {
    int version;
    char type;
    int endpoint;
    char name[PROC_NAME_LEN + 1];
    char state;
    int blocked;
    int priority;
    int user_time;
    int ticks;
    unsigned long cycle_high;
    unsigned long cycle_low;
    u64_t memory;
    unsigned long effuid;
    unsigned long nice;
    u64_t cpucycles[1];
}prev[PROC_MAX], now[PROC_MAX];

/*
 * 记录历史指令
 */
char *record[MAXHISTORY];
int rec_size = -1;

void eval(char *cmdline, int inPipe);
void preparse(char *cmdline);
int parseline(const char *cmdline, char **argv);
int builtin(char **argv, int argc);
void redirect(char **argv, int *argc);
void direct_null();
void direct_restore(int STDIN_FD_COPY, int STDOUT_FD_COPY);
char* get_pipepos(char *cmdline);
void pipeline(char *cmdline1, char *cmdline2);
void mytop();
void get_meminfo(int *totSize, int *freeSize, int *cachedSize);
void get_kinfo(int *total_proc);
void get_pidinfo(struct proc *p, int *num, u64_t *total, u64_t *idle);
void parse_file(int pid, struct proc *p, u64_t *total, u64_t *idle);

static void sleep_ms(unsigned int secs);
static inline u64_t make64(unsigned long lo, unsigned long hi);
int Open(char *path, int param);
FILE* Fopen(char *path, char *mode);
void Execvp(char *path, char **params);

/*
 * 解析一行命令
 */
void eval(char *cmdline, int inPipe) {
    if (!inPipe) { // 记录history
        record[++rec_size] = (char *)malloc(sizeof(char) * (strlen(cmdline) + 1));
        strcpy(record[rec_size], cmdline);
    }
    char *argv[MAXLINE];
    cmdline[(int)strlen(cmdline) - 1] = ' '; // 把最后的回车改成空格
    preparse(cmdline);
    int argc = parseline(cmdline, argv);
    if (argc == 0) return; // 遇到空行直接返回
    char* cmdline2 = get_pipepos(cmdline);
    if (cmdline2 != NULL) {
        *cmdline2 = '\0';
        pipeline(cmdline, cmdline2 + 1);
    } else if (!builtin(argv, argc)) {
        int wait = argv[argc - 1][0] != '&';
        // fprintf(stderr, "%d\n", wait);
        if (!wait) argv[argc - 1] = NULL, argc--;
        pid_t pid;
        if ((pid = fork()) == 0) {
            if (!wait) {
                direct_null();
                signal(SIGCHLD, SIG_IGN); // 使得minix接管该进程,即不产生僵死进程
            } else redirect(argv, &argc);
            Execvp(argv[0], argv);
        }
        int status;
        if (wait) waitpid(pid, &status, 0);
    }
}

/*
 * 在重定向前后加空格,便于后续解析
 */
void preparse(char *cmdline) {
    char newcmdline[MAXLEN];
    int len = strlen(cmdline), j = 0;
    for (int i = 0; i < len; i++) {
        if (cmdline[i] == '<') newcmdline[j++] = ' ', newcmdline[j++] = '<', newcmdline[j++] = ' '; // '<'
        else if (cmdline[i] == '>') { 
            if (cmdline[i + 1] == '>') { // '>>'
                newcmdline[j++] = ' ';
                newcmdline[j++] = '>';
                newcmdline[j++] = '>';
                newcmdline[j++] = ' ';
                i++;
            } else { // '>'
                newcmdline[j++] = ' ';
                newcmdline[j++] = '>';
                newcmdline[j++] = ' ';
            }
        } else newcmdline[j++] = cmdline[i];
    }
    newcmdline[j] = '\0';
    strcpy(cmdline, newcmdline);
}

/*
 * 解析参数,把参数存入argv,并返回argc
 */
int parseline(const char *cmdline, char **argv) {
    int len = strlen(cmdline), argc = 0, temp = 0;
    for (int i = 0; i < len; i++) {
        if (cmdline[i] == ' ') {
            if (i != 0 && cmdline[i - 1] != ' ') {
                argv[argc][temp] = '\0';
                argc++;
                temp = 0;
            }
            continue;
        }
        if (!temp) argv[argc] = (char *)malloc(sizeof(char) * MAXLEN);
        argv[argc][temp++] = cmdline[i];
    }
    if (temp == 0) argc--;
    return argc + 1;
}
/*
 * 判断是否为内置命令
 */
int builtin(char **argv, int argc) {
    if (!strcmp(argv[0], "cd")) {
        chdir(argv[1]);
        return 1;
    }
    if (!strcmp(argv[0], "history")) {
        redirect(argv, &argc);
        if (argc == 1) {
            for (int i = 0; i <= rec_size; i++) {
                printf("  %d  %s", i + 1, record[i]);
            }
        } else {
            int number;
            // fprintf(stderr, "%s\n", argv[1]);
            sscanf(argv[1], "%d", &number);
            for (int i = MAX(0, rec_size - number + 1); i <= rec_size; i++) {
                printf("  %d  %s", i + 1, record[i]);    
            }
        }
        return 1;
    }
    if (!strcmp(argv[0], "exit")) {
        exit(0);
    }
    if (!strcmp(argv[0], "mytop")) {
        redirect(argv, &argc);
        mytop();
        return 1;
    }
    return 0;
}

/*
 * 根据>, >>, <重定向标准输入输出
 */
void redirect(char **argv, int *argc) {
    for (int i = 0; i < *argc; i++) {
        if (strcmp(argv[i], "<") == 0) {
            if (i + 1 == *argc) {
                fprintf(stderr, "REDIRECT ERROR.\n");
                exit(0);
            }
            close(STDIN_FILENO);
            int file_fd = Open(argv[i + 1], O_RDONLY);

            dup(file_fd);
            *argc -= 2; // 去掉这两个参数 并把后面的参数都往前移两位
            for (int j = i; j < *argc; j++) strcpy(argv[j], argv[j + 2]);
        }
        if (strcmp(argv[i], ">") == 0) {
            if (i + 1 == *argc) {
                fprintf(stderr, "REDIRECT ERROR.\n");
                exit(0);
            }
            close(STDOUT_FILENO);
            int file_fd = Open(argv[i + 1], O_WRONLY | O_CREAT | O_TRUNC);
            dup(file_fd);
            *argc -= 2;
            for (int j = i; j < *argc; j++) strcpy(argv[j], argv[j + 2]);
        }
        if (strcmp(argv[i], ">>") == 0) {
            if (i + 1 == *argc) {
                fprintf(stderr, "REDIRECT ERROR.\n");
                exit(0);
            }
            close(STDOUT_FILENO);
            int file_fd = Open(argv[i + 1], O_WRONLY | O_CREAT | O_APPEND);
            dup(file_fd);
            *argc -= 2;
            for (int j = i; j < *argc; j++) strcpy(argv[j], argv[j + 2]);
        }
    }
    argv[*argc] = NULL; // 保证argv以NULL为结尾
}

/*
 * 把输入输出重定向到/dev/null 
 */
void direct_null() {
    int null_fd = Open("/dev/null", O_WRONLY);
    // fprintf(stderr, "%d\n", file_fd);
    /*
    if (file_fd < 0) {
        fprintf(stderr, "OPEN /dev/null ERROR.\n");
        exit(0);
    }
    */
    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);
    dup(null_fd); 
    dup(null_fd);
    dup(null_fd);
}

/*
 * 复原重定向
 */
void direct_restore(int STDIN_FD_COPY, int STDOUT_FD_COPY) {
    close(0); close(1);
    dup(STDIN_FD_COPY); dup(STDOUT_FD_COPY);
}

/*
 * 检查是否存在管道,若存在则返回管道地址
 */
char* get_pipepos(char *cmdline) {
    return strchr(cmdline, '|');    
}

/*
 * 子进程执行cmdline1,将输出通过管道作为主进程cmdline2的输入
 */
void pipeline(char *cmdline1, char *cmdline2) {
    int fd[2];
    pipe(&fd[0]);
    if (fork() == 0) { // 子进程,重定向输出
        close(fd[0]);
        close(STDOUT_FILENO);
        dup(fd[1]);
        close(fd[1]);
        eval(cmdline1, 1);
        exit(0);
    } else { // 父进程,重定向输入
        close(fd[1]);
        close(STDIN_FILENO);
        dup(fd[0]);
        close(fd[0]);
        eval(cmdline2, 1);
    }
}

/*
 * 统计内存和cpu使用情况
 */
void mytop() {  
    int totSize, freeSize, cachedSize, total_proc, proc_num_prev = 0, proc_num_now = 0;
    u64_t total_ticks_pre = 0, total_ticks_now = 0, idle_ticks_pre = 0, idle_ticks_now = 0;
    get_meminfo(&totSize, &freeSize, &cachedSize);
    printf("main memory: ");
    printf(" %dK total,", totSize);
    printf(" %dK free,", freeSize);
    printf(" %dK cached\n", cachedSize);
    get_kinfo(&total_proc);
    printf("Total procs: %d\n", total_proc);

    get_pidinfo(prev, &proc_num_prev, &total_ticks_pre, &idle_ticks_pre);
    sleep_ms(500);
    get_pidinfo(now, &proc_num_now, &total_ticks_now, &idle_ticks_now);
    double CPU_util = 100 - (double)100 * (idle_ticks_now - idle_ticks_pre) / (total_ticks_now - total_ticks_pre);
    printf("CPU utilization : %.4f%%\n", CPU_util);
}

/*
 * 读取/proc/meminfo
 */
void get_meminfo(int *totSize, int *freeSize, int *cachedSize) {
    FILE *fp;
    fp = Fopen("/proc/meminfo", "r");
    int pagesize, total, free, largest, cached;
    fscanf(fp, "%d %d %d %d %d", &pagesize, &total, &free, &largest, &cached);
    *totSize = pagesize * total / 1024;
    *freeSize = pagesize * free / 1024;
    *cachedSize = pagesize * cached / 1024;
    fclose(fp);
}
/*
 * 读取/proc/kinfo
 */
void get_kinfo(int *total_proc) {
    FILE *fp;
    fp = Fopen("/proc/kinfo", "r");
    int proc, task;
    fscanf(fp, "%d %d", &proc, &task);
    *total_proc = proc + task;
    fclose(fp);
}

/*
 * 读取/proc下所有文件夹
 */
void get_pidinfo(struct proc *p, int *num, u64_t *total, u64_t *idle) {
    DIR *p_dir;
    char *end;
    if ((p_dir = opendir("/proc/")) == NULL) {
        fprintf(stderr, "OPENDIR ERROR.\n");
        exit(1);
    }
    struct dirent *p_ent;
    *num = 0;
    for (p_ent = readdir(p_dir); p_ent != NULL; p_ent = readdir(p_dir)) {
        int pid = strtol(p_ent->d_name, &end, 10);
        if (!pid) continue;
        (*num)++;
        parse_file(pid, &p[*num], total, idle);
    }
    closedir(p_dir);
}
/*
 * 读取/proc/pid/psinfo
 */
void parse_file(int pid, struct proc *p, u64_t *total, u64_t *idle) {
    char path[MAXPATH];
    sprintf(path, "/proc/%d/psinfo", pid);
    FILE *fp = Fopen(path, "r");
    fscanf(fp, "%d", &(p->version));
    fscanf(fp, " %c", &(p->type));
    fscanf(fp, " %d", &(p->endpoint));
    fscanf(fp, " %s", p->name);
    fscanf(fp, " %c", &(p->state));
    fscanf(fp, " %d", &(p->blocked));
    fscanf(fp, " %d", &(p->priority));
    fscanf(fp, " %d", &(p->user_time));
    fscanf(fp, " %d", &(p->ticks));
    fscanf(fp, " %lu", &(p->cycle_high));
    fscanf(fp, " %lu", &(p->cycle_low));
    fscanf(fp, " %llu", &(p->memory));
    fscanf(fp, " %lu %lu", &(p->effuid), &(p->nice));
    p->cpucycles[0] = make64(p->cycle_low, p->cycle_high);
    *total += p->cpucycles[0];
    if (p->endpoint == -4) 
        *idle = p->cpucycles[0];
    // printf("PID : %d  CYCLE : %llu  low : %lu  high : %lu  ticks : %d  %c  %c\n", pid, p->cpucycles[0], p->cycle_low, p->cycle_high, p->ticks, p->type, p->state);
    fclose(fp);
}

/*
 * 合并高低周期
 */
static inline u64_t make64(unsigned long lo, unsigned long hi) {
    return ((u64_t)hi << 32 | (u64_t)lo);
}

/*
 * 实现毫秒级的sleep
 */
static void sleep_ms(unsigned int secs) {
    struct timeval tval;
    tval.tv_sec=secs/1000;
    tval.tv_usec=(secs*1000)%1000000;
    select(0,NULL,NULL,NULL,&tval);
}

/*
 * 封装带有错误处理的open函数
 */
int Open(char *path, int param) {
    int file_fd;
    if ((file_fd = open(path, param)) < 0) {
        fprintf(stderr, "OPEN FILE ERROR.\n");
        exit(1);
    } else return file_fd;

}
/*
 * 封装带有错误处理的fopen函数
 */
FILE* Fopen(char *path, char *mode) {
    FILE* fp;
    if ((fp = fopen(path, mode)) == NULL) {
        fprintf(stderr, "FOPEN ERROR.\n");
        exit(1);
    }
    return fp;
}
/*
 * 封装带有错误处理的execvp函数
 */
void Execvp(char *path, char **params) {
    if (execvp(path, params) < 0) {
        fprintf(stderr, "EXECVP ERROR.\n");
        exit(1);
    }
}

int main() {
    char cmdline[MAXLINE];
    int STDIN_FD_COPY = dup(STDIN_FILENO); //保存标准输入输出文件符,用于复原
    int STDOUT_FD_COPY = dup(STDOUT_FILENO);
    while(1) {
        printf(" myshell> ");
        fgets(cmdline, MAXLINE, stdin);
        eval(cmdline, 0);
        direct_restore(STDIN_FD_COPY, STDOUT_FD_COPY);
    }
    return 0;
}