OP-TEE+qemu的启动过程分析--run-only目标执行
使用qemu模拟运行OP-TEE的时候,是通过在在build目录中执行make run-only来实现的。
1.run-only目标内容
在Makefile(qemu.mk)文件中关于run-only目标的定义如下:
.PHONY: run-only
run-only:
$(call check-terminal)
$(call run-help)
$(call launch-terminal,54320,"Normal World")
$(call launch-terminal,54321,"Secure World")
$(call wait-for-ports,54320,54321)
$(QEMU_PATH)/arm-softmmu/qemu-system-arm \
-nographic \
-serial tcp:localhost:54320 -serial tcp:localhost:54321 \
-s -S -machine virt -machine secure=on -cpu cortex-a15 \
-m 1057 \
-bios $(ROOT)/out/bios-qemu/bios.bin \
$(QEMU_EXTRA_ARGS)
run-only目标中执行的相关函数都会在build对应的文件中进行定义,下面大致介绍以下上述函数的功能:
$(call check-terminal):check-terminal在qemu的工程中不会被定义,该语句不会被执行,但是在其他工程中会被定义,具体请查看build/common.mk文件
$(call run-help):run-help函数定义在build/commom.mk文件中,主要用来打印出相关的启动帮助信息
$(call launch-terminal,54320,"Normal World"):执行 launch-terminal,54320,"Normal World",其中launch-terminal在build/common.mk文件中被定义
$(call launch-terminal,54321,"Secure World"):执行功能同上,只是重定向时将端口换成了54321,且启动的terminal名字为Secure World
$(call wait-for-ports,54320,54321):调用wait-for-prots函数,该函数定义在build/common.mk文件中,主要功能是检查上面启动的两个terminal的通过socket通信是否正常
$(QEMU_PATH)/arm-softmmu/qemu-system-arm :该指令就是调用qemu-system-arm指令并设定好qemu启动的各种参数,然后开始启动linux跟OP-TEE,该指令被完全展开之后如下:
/home/icyshuai/devel/optee/build/../qemu/arm-softmmu/qemu-system-arm \
-nographic \
-serial tcp:localhost:54320 -serial tcp:localhost:54321 \
-s -S -machine virt -machine secure=on -cpu cortex-a15 \
-m 1057 \
-bios /home/icyshuai/devel/optee/build/../out/bios-qemu/bios.bin \
-nographic :不显示图形界面
-serial:将串口重定向到后面的参数部分
-S:使用C来控制启动(在qemu的console界面输入C之后才会正式启动系统)
-m:设定虚拟的内存大小
-bios:指定BIOS的文件(该image中会包含OP-TEE,linux,rootfs的镜像)
执行完成之后,然后在qemu的console端输入字母c,然后回车就开始正式的启动linux+OP-TEE
2.launch-terminal函数
launch-term函数的主要功能是用来启动terminal,该函数定义在build/common.mk文件中。具体内容如下:
define launch-terminal
@nc -z 127.0.0.1 $(1) || \
$(gnome-terminal) -t "$(2)" -x $(SOC_TERM_PATH)/soc_term $(1) &
endef
$(gnome-terminal)的定义也在common.mk文件中,定义如下:
gnome-terminal := $(shell command -v gnome-terminal 2>/dev/null)
所以当调用$(call launch-terminal, 54320, "Normal World")等价于
gnome-terminal -t "Normal World" -x $(SOC_TERM_PATH)/soc_term 54320
该函数的调用的作用是启动一个名字为Normal World的terminal,并且在terminal中执行"soc_term 54320",而soc_term就是在soc_term目录中编译出来的可执行文件。执行soc_term 54320命令的主要作用是将该terminal的输入和输出通过54320端口重新定向标准输入和输出端。
3.soc_term可执行文件
soc_term可执行文件是用来实现normal world和secure world两个terminal输入和输出重定向到标准输入输出端口,该可执行文件的source code存放在soc_term目录中。soc_term.c文件中的main函数定义如下:
int main(int argc, char *argv[])
{
int listen_fd;
char *port;
bool have_handle_telnet_option = false;
switch (argc) {
case 2:
port = argv[1];
break;
case 3:
if (strcmp(argv[1], "-t") != 0)
usage();
have_handle_telnet_option = true;
port = argv[2];
break;
default:
usage();
}
save_current_termios();//获取当前的terminal的信息(标准输入输出的terminal配置)
listen_fd = get_listen_fd(port);//建立socket机制,并监听输入的端口号
printf("listening on port %s\n", port);
if (have_handle_telnet_option) //判定是否使用telent
printf("Handling telnet commands\n");
/* 进入loop循环,完成端口监听和输入输出的重定向 */
while (true) {
int fd = accept_fd(listen_fd); //开始接收建立的监听端口的信息
handle_telnet = have_handle_telnet_option;
handle_telnet_codes(-1, NULL, NULL); /* Reset internal state *///为使用telent时不起作用
warnx("accepted fd %d", fd);
set_tty_noncanonical(); //拷贝当前terminal的信息,并配置其他参数,然后条用tcsetattr函数来设定当前启动的terminal的信息
serve_fd(fd); //开始处理监听收到的数据,并根据对应的revent进行重定向操作,server_fd函数的注释见后续章节。
/* 处理完成之后,fd */
if (close(fd))
err(1, "close");
fd = -1;
/* 保存当前terminos的配置 */
restore_termios();
}
}
server_fd函数主要完成接收到的监控端口的数据,并执行重定向操作。代码内容和解释如下:
static void serve_fd(int fd)
{
uint8_t buf[512];
struct pollfd pfds[2];
/* 设定pollfd参数,用于实现重定向操作 */
memset(pfds, 0, sizeof(pfds));
pfds[0].fd = STDIN_FILENO;
pfds[0].events = POLLIN;
pfds[1].fd = fd;
pfds[1].events = POLLIN;
while (true) {
size_t n;
/* 获取监听事件的pfds[0]和pfds[1]中定义的事件 */
if (poll(pfds, 2, -1) == -1)
err(1, "poll");
/* 如果pfds[0]中的POLLIN时间触发(在该terminal的标准输入有输入操作),则进行读取操作 */
if (pfds[0].revents & POLLIN) {
n = read(STDIN_FILENO, buf, sizeof(buf)); //从该terminal的标准输入端口中读取输入的数据
if (n == -1)
err(1, "read stdin");
if (n == 0)
errx(1, "read stdin EOF");
/* TODO handle case when this write blocks */
/* 将读取到的数据写入到重定向的port捆绑的socket */
if (!write_buf(fd, buf, n)) {
warn("write_buf fd");
break;
}
}
/* 如果pfds[1]中的POLLIN时间触发(监测到该terminal的port捆绑的socket有输入流操作),则读取监测的port对应的socket句柄中的数据 */
if (pfds[1].revents & POLLIN) {
n = read(fd, buf, sizeof(buf)); //读取与port捆绑的socket的句柄中的数据
if (n == -1) {
warn("read fd");
break;
}
if (n == 0) {
warnx("read fd EOF");
break;
}
handle_telnet_codes(fd, buf, &n);
/* 将读取到的数据写入到该terminal的标准输出 */
if (!write_buf(STDOUT_FILENO, buf, n))
err(1, "write_buf stdout");
}
}
}