shell程序为操作系统的外壳程序,也就是与用户交互的程序。它为用户提供可操作的界面,负责接收用户的输入,然后执行一系列的程序,并为用户提供输出内容。首先在system程序中加入载入shell程序的功能:
//启动shell程序
printf("[ OK ] Start shell program.\n");
install_program("/usr/bin/shell", "");
其中install_program()函数的功能非常简单,就是调用了0x80号中断程序,载入/usr/bin/shell这个程序并运行:
void install_program(char *path, char *args)
{
int status = 0;
void *ret = NULL;
int params[6];
params[0] = 0;
params[1] = (int) path;
params[2] = (int) args;
params[3] = (int) NULL;
params[4] = (int) &ret;
params[5] = (int) &status;
__asm__ volatile("int $0x80" :: "a"(params));
}
下面我们来编写shell.c。我们需要让shell程序具有可以执行其它程序的功能。换句话说,就是要让shell程序可以接收用户输入的命令,并根据这个命令找到文件系统中所对应的可执行程序,最后运行这些程序。我们需要为shell程序完成两个功能:校验登录和接收并执行用户命令。我们先来实现登录功能:
//登录系统
void login()
{
//用户名
char *username = malloc(USER_U_LEN);
//密码
char *passwd = malloc(USER_P_LEN);
//如果未登录,则一直提示需要先登录
while (session->status == SESSION_STATUS_NOLOGIN)
{
//显示登录信息
printf("LidqOS login: ");
//接收用户名
gets(username);
//显示输入密码
puts("Password: ");
//取得密码
char *p = passwd;
//接收密码字符
while ((*p = getchar()) != '\n')
{
//不显示密码内存或*号
//putchar('*');
p++;
}
putchar('\n');
*p = '\0';
//校验用户名与密码是否有效
check_passwd(username, passwd);
}
free(username);
free(passwd);
}
//校验用户名和密码
int check_passwd(char *username, char *passwd)
{
//校验lidq
if (str_compare(username, "lidq") == 0 && str_compare(passwd, "lidq") == 0)
{
//设置session中的用户名
str_copy(username, session->username);
//主目录
str_copy("/home/lidq/", session->home);
//当前工作目录
str_copy(session->home, session->current_path);
//用户id
session->uid = 1;
//用户组id
session->gid = 1;
//设定为已登录
session->status = SESSION_STATUS_LOGIN;
//显示登录成功
printf("Login successful.\n");
//返回成功
return 1;
}
//校验失败
else
{
//显示登录被拒绝
printf("Login incorrect.\n");
}
//返回失败
return 0;
}
接收用户命令,如果命令为exit则退出,否则执行此命令:
//始终循环
while (1)
{
//登录
login();
//申请接收用户的命令
char *cmd = malloc(SHELL_CMD_LEN);
//如果尚已登录成功
while (session->status == SESSION_STATUS_LOGIN)
{
//取得当前工作目录
get_current_folder_name(session->current_folder_name);
//显示命令提示
printf("[%s@%s %s]: ", session->username, computer_name, session->current_folder_name);
//接收用户命令
gets(cmd);
//如果用户输入了exit,则退出
if (str_compare(SHELL_CMD_EXIT, cmd) == 0)
{
break;
}
//执行用户输入的命令
execute_cmd(cmd);
}
//释放命令内存
free(cmd);
//退出
logout();
}
在shell程序执行用户命令时有两个地方需要说明一下。假设用户所在的工作目录为“/usr/”,环境变量为“/usr/bin/”。首先在执行用户输入的命令时先要判断这个命令的开始字符:
如果是“/”说明此命令所使用的目录为绝对目录,如“/usr/bin/example”;
如果此命令是以“./”或“../”开头说明此命令所使用的目录为相对目录,如“./bin.example”,这时要根据用户当前所在的目录与此目录组合成一个绝对目录来查找文件,将“/usr/”与“./bin/example”组合成“/usr/bin/example”;
/*
* 查找命令的完整路径
*/
void find_cmd_in_path(char *cmd, char* out)
{
//以'/'开头: 绝对路径
if (cmd[0] == '/')
{
repath(cmd, NULL, out);
}
//不是以'/'开头: 相对路径
else if ((cmd[0] == '.' && cmd[1] == '/') || (cmd[0] == '.' && cmd[1] == '.' && cmd[2] == '/'))
{
repath(cmd, session->current_path, out);
}
//使用环境变量
else
{
str_append(session->PATH, cmd, out);
}
}
其中repath()函数的内容是以栈的方式来处理目录,如遇普通目录则入栈;如遇“./”表示当前目录,不做处理;如遇“../”表示父目录,则出栈。此函数的具体内容不再列出。
通过上面 find_cmd_in_path()函数取得了程序所在的绝对路径之后再通过调用0x80号中断服务来安装并尝试运行此程序:
/*
* 安装程序
*/
void install_program(char *path, char *args)
{
//返回状态
int status = 0;
//程序运行信号量
u32 sem_addr = 0;
int params[6];
params[0] = 0;
params[1] = (int) path;
params[2] = (int) args;
params[3] = (int) session;
params[4] = (int) &sem_addr;
params[5] = (int) &status;
__asm__ volatile("int $0x80" :: "a"(params));
//如果状态为0,说明安装程序成功
if (status == 0)
{
//等待程序运行结束信号量
sem_wait_shell(sem_addr);
//发送release程序信号量
sem_post_shell(sem_addr + sizeof(s_sem));
}
//如果状态为1,说明没有这个文件
else if (status == 1)
{
printf("-bash: \"%s\": command not found.\n", path);
}
//如果状态为2,说明这不是一个可执行文件
else if (status == 2)
{
printf("-bash: \"%s\": is not an executing program.\n", path);
}
//其它:为程序运行申请内存是失败
else
{
printf("-bash: \"%s\": alloc memory error.\n", path);
}
}
当载入程序之后,需要判断是否成功,如果成功则说明该命令已经运行,这里我们需要把shell程序阻塞起来,当该命令运行结束之后再唤醒shell程序,而当shell程序被唤醒之后,需要再发送一个信号量将通知命令可以被销毁。这两个信号量被定义在pcb中:
/*
* 进程控制块
*/
typedef struct process_control_block
{
//进程号
u32 process_id;
... ...
//用户登录信息
s_session session;
//内存申请表
void *alloc_list;
//shell运行pcb的同步信号量
s_sem sem_shell[2];
} s_pcb;
重新修改start_main()函数让其为main传入实际的参数:
void start_main()
{
//为main函数传入参数个数
int argc = 0;
//为main函数传入参数表
char **args = NULL;
//运行参数
char *pars = malloc(CMD_ARGS_SIZE);
int params[2];
params[0] = 5;
params[1] = (int) pars;
//取得pcb的运行参数内容
__asm__ volatile("int $0x80" :: "a"(params));
//计算参数个数
argc = str_split_count(pars, ' ');
//为二级字符串指针申请内存
args = malloc(argc * sizeof(char *));
//为每个字符串申请内存
for (int i = 0; i < argc; ++i)
{
//申请内存
args[i] = malloc(CMD_ARGS_EVERY_SIZE);
}
//将字符串参数分割为字符串数组
str_split(pars, ' ', args);
//调用main函数,并传入参数
main(argc, args);
//释放main函数参数所占用的内存
for (int i = 0; i < argc; ++i)
{
free(args[i]);
}
//释放内存
free(args);
free(pars);
//type为1,代表停止进程
params[0] = 1;
//请求0x80号中断服务停止此进程
__asm__ volatile("int $0x80" :: "a"(params));
}
好了,现在编写shell的工作已经完成,让我们来编写一个用户测试的程序example_args.c用来显示用户输入的命令:
#include
int main(int argc, char **args)
{
printf("Example args Start:\n");
for (int i = 0; i < argc; i++)
{
printf("%d. %s\n", i, args[i]);
}
printf("Example finished.\n");
return 0;
}
最后修改Makefile加入新的编译内容并运行,出现shell程序的内容,输入用户名和密码并执行example_args命令。这里采用的密码输入借鉴了Linux密码输入方式,不显示输入的密码相关内容,包括“*”号:
LidqOS login: lidq
Password: lidq
[lidq@lidq-OS lidq/]: example_args -t Text -s Code -f Cleanup
源代码的下载地址为:
https https://github.com/magicworldos/lidqos.git
git git@github.com:magicworldos/lidqos.git
subverion https://github.com/magicworldos/lidqos
branch v0.29
Copyright © 2015-2023 问渠网 辽ICP备15013245号