16.1.shell是什么鬼
16.1.1、壳与封装
(1)shell就是壳的意思,在计算机中经常提到shell是用户操作接口的意思。
(2)因为计算机程序本身很复杂,里面的实现和外面的调用必须分开。接口本身就是对内部复杂的实现的一种封装,外部只需要通过接口就可以很容易的实现效果,但是却不用理会内部实现的复杂性和原理。
16.1.2、程序或操作系统的用户接口
(1)操作系统运行起来后都会给用户提供一个操作界面,这个操作界面就叫shell。用户可以通过shell来调用操作系统内部的复杂实现。
(2)shell编程就是在shell层次上进行编程。譬如linux中的脚本编程、windows中的批处理。
16.1.3、两种shell:GUI和cmdline
(1)GUI(图形用户界面),特点是操作简单、易学易用,适合使用电脑来工作的人。
(2)cmdline(命令行界面),譬如linux的终端和windows的cmd,特点是不易用易学,优点是可以进行方便的shell编程,适合做开发的人。
(3)展望:将来的shell应该是声音图像等接口的。
16.1.4、shell的运行原理:由消息接收、解析、执行构成的死循环
(1)我们主要分析命令行shell的运行原理。
(2)命令行shell其实就是一个死循环。这个死循环包含3个模块,这3个模块是串联的,分别是命令接收、命令解析、命令执行。
(3)命令行有一个标准命令集,用户在操作的时候必须知道自己想要的操作用通过哪个命令来实现,不能随便输入命令。如果用户输入了一个不是标准命令的命令(不能识别的命令),提示用户这不是一个合法命令,然后重新回到命令行让用户输入下一个命令。
(4)用户输入命令的界面是一个命令行,命令行的意思就是用户输入的命令是以行为单位的,更好理解的说用户输入的命令在用户按下回车键之后就算是结束了,shell可以开始接收了。
16.1.5、shell举例:uboot、linux终端、Windows图形界面等
(1)常见的shell,uboot就是一个裸机程序构成的shell(本课程要完成的shell也是裸机的),clinux中断和windows的cmd是操作系统下的命令行shell。windows图形界面、ubuntu图形界面、android的图形界面这些都是图形界面的shell。突然想到另一个类型的shell,网页类型的shell,典型代表就是路由器。
16.2.shell实战1-从零写最简单shell
16.2.1、使用printf和scanf做输入回显
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
|
#include <stdio.h>
#include <string.h>
#define MAX_LINE_LENGTH 256 // 命令行长度,命令不能超过这个长度
int
main(
void
)
{
char
str[MAX_LINE_LENGTH];
// 用来存放用户输入的命令内容
while
(1)
{
// 打印命令行提示符,注意不能加换行
printf
(
"aston#"
);
// 清除str数组以存放新的字符串
memset
(str, 0,
sizeof
(str));
// shell第一步:获取用户输入的命令
scanf
(
"%s"
, str);
// shell第二步:解析用户输入命令
// shell第三步:处理用户输入命令
printf
(
"%s\n"
, str);
}
return
0;
}
|
16.2.2、定义简单命令集
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
63
64
|
#include <stdio.h>
#include <string.h>
#define MAX_LINE_LENGTH 256 // 命令行长度,命令不能超过这个长度
// 宏定义一些标准命令
#define led "led"
#define lcd "lcd"
#define pwm "pwm"
#define CMD_NUM 3 // 当前系统定义的命令数
char
g_cmdset[CMD_NUM][MAX_LINE_LENGTH];
// 初始化命令列表
static
void
init_cmd_set(
void
)
{
memset
(g_cmdset, 0,
sizeof
(g_cmdset));
// 先全部清零
strcpy
(g_cmdset[0], led);
strcpy
(g_cmdset[1], lcd);
strcpy
(g_cmdset[2], pwm);
}
int
main(
void
)
{
int
i = 0;
char
str[MAX_LINE_LENGTH];
// 用来存放用户输入的命令内容
init_cmd_set();
while
(1)
{
// 打印命令行提示符,注意不能加换行
printf
(
"poi_carefree#"
);
// 清除str数组以存放新的字符串
memset
(str, 0,
sizeof
(str));
// shell第一步:获取用户输入的命令
scanf
(
"%s"
, str);
// shell第二步:解析用户输入命令
for
(i=0; i<CMD_NUM; i++)
{
if
(!
strcmp
(str, g_cmdset[i]))
{
// 相等,找到了这个命令,就去执行这个命令所对应的动作。
printf
(
"您输入的命令是:%s,是合法的\n"
, str);
break
;
}
}
if
(i >= CMD_NUM)
{
// 找遍了命令集都没找到这个命令
printf
(
"%s不是一个内部合法命令,请重新输入\n"
, str);
}
// 第三步的处理已经移到前面分开处理了,所以这里不需要了
// shell第三步:处理用户输入命令
}
return
0;
}
|
16.3.shell实战2-将简易shell移植到开发板中
16.3.1、工程选定、文件复制、Makefile书写
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
|
CC = arm-linux-gcc
LD = arm-linux-ld
OBJCOPY = arm-linux-objcopy
OBJDUMP = arm-linux-objdump
AR = arm-linux-ar
INCDIR := $(shell pwd)
# C预处理器的flag,flag就是编译器可选的选项
CPPFLAGS := -nostdlib -nostdinc -I$(INCDIR)/include
# C编译器的flag
CFLAGS := -Wall -O2 -fno-builtin
#导出这些变量到全局,其实就是给子文件夹下面的Makefile使用
export CC LD OBJCOPY OBJDUMP AR CPPFLAGS CFLAGS
objs := start.o led.o
clock
.o uart.o main.o stdio.o
uart.bin: $(objs)
$(LD) -Tlink.lds -o uart.elf $^
$(OBJCOPY) -O binary uart.elf uart.bin
$(OBJDUMP) -D uart.elf > uart_elf.dis
gcc mkv210_image.c -o mkx210
./mkx210 uart.bin 210.bin
lib/libc.a:
cd lib; make; cd ..
%.o : %.S
$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c
%.o : %.c
$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c
clean:
rm *.o *.elf *.bin *.dis mkx210 -f
|
16.3.2、Main函数
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
|
void
puts
(
const
char
*p);
char
*
gets
(
char
*p);
void
uart_init(
void
);
// C标准库中也有个memset函数,但是我们这里用的是自己写的,没用标准库
void
memset
(
char
*p,
int
val,
int
length)
{
int
i;
for
(i=0; i<length; i++)
{
p[i] = val;
}
}
int
main(
void
)
{
char
buf[100] = {0};
// 用来暂存用户输入的命令
uart_init();
puts
(
"x210 simple shell:\n"
);
while
(1)
{
puts
(
"aston#"
);
memset
(buf, 0,
sizeof
(buf));
// buf弄干净好存储这次用户输入
gets
(buf);
// 读取用户输入放入buf中
puts
(
"您输入的是:"
);
puts
(buf);
puts
(
"\n"
);
}
return
0;
}
|
16.3.3、printf和scanf函数(本质是putc和getc函数)的移植
(1)puts和putchar函数比较简单,注意的地方就是windows和linux中的回车键定义的不同。
所以在putchar函数中如果用户要输出'\n'时,实际输出"\r\n"
(2)gets和getchar是从Windows中的SecureCRT终端输入字符串到裸机程序中。这里面至少有2个问题:用户输入回显问题、用户按回车键问题、用户按BackSpace退格键问题
(3)自己实现回显
(4)用户按回车键问题,在getchar中解决。方法是碰到'\r'时直接返回'\n'
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
void
uart_putc(
char
c);
char
uart_getc(
void
);
// 从stdio输出一个字符c
void
putchar
(
char
c)
{
// 碰到用户输出'\n'时,实际输出"\r\n"
// windows中按下回车键等效于"\r\n",在linux中按下回车键等效于'\n'
if
(c ==
'\n'
)
uart_putc(
'\r'
);
/*
if (c == '\b')
{
uart_putc('\b');
uart_putc(' ');
}
*/
uart_putc(c);
}
// 从stdio输出一个字符串p
void
puts
(
const
char
*p)
{
while
(*p !=
'\0'
)
{
putchar
(*p);
p++;
}
}
// 从stdio输入一个字符
char
getchar
(
void
)
{
char
c;
c = uart_getc();
if
(c ==
'\r'
)
{
return
'\n'
;
}
return
c;
}
// 从stdio输入一个字符串
// 返回值指向传进来的数组的首地址的,目的是实现函数的级联调用
char
*
gets
(
char
*p)
{
char
*p1 = p;
char
ch;
// 用户的一次输入是以'\n'为结束标志的
while
((ch =
getchar
()) !=
'\n'
)
{
// 回显
if
(ch !=
'\b'
)
{
// 用户输入的不是退格键
putchar
(ch);
// 回显
*p++ = ch;
// 存储ch,等效于 *p = ch; p++;
}
else
{
// 用户输入的是退格键
// \b只会让secureCRT终端输出指针向后退一格,但是那个要删掉的字符还在
// 删掉的方法就是下面3行
if
(p > p1)
{
putchar
(
'\b'
);
putchar
(
' '
);
putchar
(
'\b'
);
// 3行处理退格键回显
p--;
// 退一格,指针指向了要删除的那个格子
*p =
'\0'
;
// 填充'\0'以替换要删除的那个字符
}
}
}
// 遇到'\n'行结束,添加'\0'作为字符串结尾。
*p =
'\0'
;
putchar
(
'\n'
);
return
p1;
}
|
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
63
64
65
66
|
#define GPA0CON 0xE0200000
#define UCON0 0xE2900004
#define ULCON0 0xE2900000
#define UMCON0 0xE290000C
#define UFCON0 0xE2900008
#define UBRDIV0 0xE2900028
#define UDIVSLOT0 0xE290002C
#define UTRSTAT0 0xE2900010
#define UTXH0 0xE2900020
#define URXH0 0xE2900024
#define rGPA0CON (*(volatile unsigned int *)GPA0CON)
#define rUCON0 (*(volatile unsigned int *)UCON0)
#define rULCON0 (*(volatile unsigned int *)ULCON0)
#define rUMCON0 (*(volatile unsigned int *)UMCON0)
#define rUFCON0 (*(volatile unsigned int *)UFCON0)
#define rUBRDIV0 (*(volatile unsigned int *)UBRDIV0)
#define rUDIVSLOT0 (*(volatile unsigned int *)UDIVSLOT0)
#define rUTRSTAT0 (*(volatile unsigned int *)UTRSTAT0)
#define rUTXH0 (*(volatile unsigned int *)UTXH0)
#define rURXH0 (*(volatile unsigned int *)URXH0)
// 串口初始化程序
void
uart_init(
void
)
{
// 初始化Tx Rx对应的GPIO引脚
rGPA0CON &= ~(0xff<<0);
// 把寄存器的bit0~7全部清零
rGPA0CON |= 0x00000022;
// 0b0010, Rx Tx
// 几个关键寄存器的设置
rULCON0 = 0x3;
rUCON0 = 0x5;
rUMCON0 = 0;
rUFCON0 = 0;
// 波特率设置 DIV_VAL = (PCLK / (bps x 16))-1
// PCLK_PSYS用66MHz算 余数0.8
//rUBRDIV0 = 34;
//rUDIVSLOT0 = 0xdfdd;
// PCLK_PSYS用66.7MHz算 余数0.18
// DIV_VAL = (66700000/(115200*16)-1) = 35.18
rUBRDIV0 = 35;
// (rUDIVSLOT中的1的个数)/16=上一步计算的余数=0.18
// (rUDIVSLOT中的1的个数 = 16*0.18= 2.88 = 3
rUDIVSLOT0 = 0x0888;
// 3个1,查官方推荐表得到这个数字
}
// 串口发送程序,发送一个字节
void
uart_putc(
char
c)
{
// 串口发送一个字符,其实就是把一个字节丢到发送缓冲区中去
// 因为串口控制器发送1个字节的速度远远低于CPU的速度,所以CPU发送1个字节前必须
// 确认串口控制器当前缓冲区是空的(意思就是串口已经发完了上一个字节)
// 如果缓冲区非空则位为0,此时应该循环,直到位为1
while
(!(rUTRSTAT0 & (1<<1)));
rUTXH0 = c;
}
// 串口接收程序,轮询方式,接收一个字节
char
uart_getc(
void
)
{
while
(!(rUTRSTAT0 & (1<<0)));
return
(rURXH0 & 0xff);
}
|
|