路径解析转换
路径有绝对路径和相对路径之分,在平常使用linux的过程中,我们在输入命令和参数的时候,往往使用的都是相对路径。相对路径是基于当前的工作路径的出的。
当前工作路径 + 相对路径 = 绝对路径
比如说当前工作路径为 /home/work 此时输入 ls file,那么file所在的绝对路径就是 /home/work/file 了。
因为这个kernel比较简单,如果路径以根目录/ 开头,就会被认为是绝对路径,其他的路径都会被认为是相对路径,会将其转换成绝对路径来使用。
以下是路径转换函数
1 | /* 将路径old_abs_path中的..和.转换为实际路径后存入new_abs_path */ |
路径转换完成之后接下来就来实现几个常用的shell。
ls
ls的命令支持以下形式 ls -l, ls -h, ls filename,它们的功能自不必多说,函数的前半部分对其参数进行解析,后半部分根据参数对应的功能构造好路径,将对应的信息输出出来。
1 | /* ls命令的内建函数 */ |
cd
1 | /* cd命令的内建函数 */ |
mkdir
1 | /* mkdir命令内建函数 */ |
rmdir
1 | /* rmdir命令内建函数 */ |
rm
1 | /* rm命令内建函数 */ |
ps
1 | /* ps命令内建函数 */ |
clear
1 | /* clear命令内建函数 */ |
pwd
1 | /* pwd命令的内建函数 */ |
上面的很多对文件和目录操作的命令都是在文件系统中实现的,这里只是对其进一步的封装。
接下来就要为这些命令提供统一的调用接口,也就是shell拉。
shell
shell在执行的时候肯定要对用户的输入进行解析,根据解析结果找到对应的处理程序,然后调用执行,首先上命令解析函数
该函数主要是对命令进行拆分,根据其拆分标志,一般是空格,将一个个分割好的参数存入argv中。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48/* 分析字符串cmd_str中以token为分隔符的单词,将各单词的指针存入argv数组 */
static int32_t cmd_parse(char *cmd_str, char **argv, char token)
{
assert(cmd_str != NULL);
int32_t arg_idx = 0;
while (arg_idx < MAX_ARG_NR)
{
argv[arg_idx] = NULL;
arg_idx++;
}
char *next = cmd_str;
int32_t argc = 0;
/* 外层循环处理整个命令行 */
while (*next)
{
/* 去除命令字或参数之间的空格 */
while (*next == token)
{
next++;
}
/* 处理最后一个参数后接空格的情况,如"ls dir2 " */
if (*next == 0)
{
break;
}
argv[argc] = next;
/* 内层循环处理命令行中的每个命令字及参数 */
while (*next && *next != token)
{ // 在字符串结束前找单词分隔符
next++;
}
/* 如果未结束(是token字符),使tocken变成0 */
if (*next)
{
*next++ = 0; // 将token字符替换为字符串结束符0,做为一个单词的结束,并将字符指针next指向下一个字符
}
/* 避免argv数组访问越界,参数过多则返回0 */
if (argc > MAX_ARG_NR)
{
return -1;
}
argc++;
}
return argc;
}
接下来就上shell拉。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63void my_shell(void)
{
cwd_cache[0] = '/';
while (1)
{
print_prompt();
memset(final_path, 0, MAX_PATH_LEN);
memset(cmd_line, 0, MAX_PATH_LEN);
readline(cmd_line, MAX_PATH_LEN);
if (cmd_line[0] == 0)
{
// 若只键入了一个回车
continue;
}
argc = -1;
argc = cmd_parse(cmd_line, argv, ' ');
if (argc == -1)
{
printf("num of arguments exceed %d\n", MAX_ARG_NR);
continue;
}
if (!strcmp("ls", argv[0]))
{
buildin_ls(argc, argv);
}
else if (!strcmp("cd", argv[0]))
{
if (buildin_cd(argc, argv) != NULL)
{
memset(cwd_cache, 0, MAX_PATH_LEN);
strcpy(cwd_cache, final_path);
}
}
else if (!strcmp("pwd", argv[0]))
{
buildin_pwd(argc, argv);
}
else if (!strcmp("ps", argv[0]))
{
buildin_ps(argc, argv);
}
else if (!strcmp("clear", argv[0]))
{
buildin_clear(argc, argv);
}
else if (!strcmp("mkdir", argv[0]))
{
buildin_mkdir(argc, argv);
}
else if (!strcmp("rmdir", argv[0]))
{
buildin_rmdir(argc, argv);
}
else if (!strcmp("rm", argv[0]))
{
buildin_rm(argc, argv);
}
else
{
printf("external command\n");
}
}
}
在上一节中说了,执行这个shell的是init进程fork出的子进程,所以要将这个myshell函数放入init进程中,当fork返回值为0时,就执行myshell1
2
3
4
5
6
7
8
9
10
11
12
13void init()
{
pid_t pid = fork();
if(fork)
{
// ...
}
else (fork == 0)
{
myshell();
}
}
这就是init进程目前所做的工作,它是在内核主线程创建之前就已经创建好了的进程。
接下来就运行一下,享受一下成果。
可以看到确实能进行一定程度上的交互了,但是目前的命令都是内部命令,写死在程序中的,这样想新增命令时非常不方便,所以后面要实现加载用户程序,让shell支持外部命令。