210 likes | 668 Views
Bionic libc & ucore. 沈彤 2008012108 张 超 2008012100. Bionic libc 编译. 前 期:只下载 bionic libc 的源码 修改 bionic libc 使之能编译通过,东拼西凑,费力不讨好 隐患:很多宏不知道是否应该定义,可能对代码造成较大影响 后期:使用 Android-x86 项目 无需 再手动修改 bionic libc Android-x86 项目已经实现了 Android 在部分 x86 机型上的运行,正确性把握较大. s yscall 连接.
E N D
Bionic libc & ucore 沈彤 2008012108 张超 2008012100
Bionic libc 编译 • 前期:只下载bionic libc的源码 • 修改bionic libc使之能编译通过,东拼西凑,费力不讨好 • 隐患:很多宏不知道是否应该定义,可能对代码造成较大影响 • 后期:使用Android-x86项目 • 无需再手动修改bionic libc • Android-x86项目已经实现了Android在部分x86机型上的运行,正确性把握较大
syscall 连接 • 将ucore中原有的syscall调用全部改为int 0x81,将bionic libc使用的syscall调用设置为int 0x80 • 好处:两套syscall机制不会互相干扰,ucore原有的机制不会被破坏;同时bionic libc的syscall方便单独进行调试 • 坏处:有不少几乎完全相同的syscall,需要重复写一次
syscall实现 • 多数syscall可以直接利用ucore已有的syscall。 • 部分可以用ucore有,但没有用到过的函数,比如利用vfs_readlink()完成readlink()系统调用。 • 此外还有一些需要改变接口,如brk, mmap的参数、返回值不一样。 • 剩下的就是需要我们自己实现的,比如signal。
signal data struct • sigset_t信号集合 用uint64_t替代uint32_t[2] • sigpending, sigqueue挂起队列 • sigaction信号处理函数 • signal_struct信号描述符 • sighand_struct信号处理描述符 • sigframe神奇的结构
signal syscalls • kill()和tkill() 产生并发送信号 • sigaction() 设置信号处理函数 • sigprocmask() 设置或解除阻塞信号 • sigpending() 获取挂起的阻塞信号 • sigsuspend() 等待一些信号 • sigaltstack() 设置信号处理函数栈地址
产生信号一个进程 • do_tkill() 得到进程描述符,关中断、获取信号锁,调用specific_send_sig_info(),释放锁、开中断。 • specific_send_sig_info() 检查信号是否会被忽略,检查信号是否是非实时的,调用send_signal() 将信号加入进程的私有等待队列中。对非阻塞信号,调用signal_wakeup() 将进程唤醒。
产生信号线程组 • do_kill() 调用group_send_sig_info() • group_send_sig_info() 检查信号是否需要被产生,调用send_signal()将其加入进程的共享等待队列中。 • handle_stop_signal() 实现信号的相互屏蔽。 • group_complete_signal() 从线程组中选择一个合适的进程,并用signal_wakeup()唤醒它。
默认的处理 • 中断结束时调用do_signal() • do_signal() 不断的调用dequeue_signal() 获取下一个信号。 • 对每个信号,如果是SIGKILL或SIGSTOP则直接终止或停止该进程所在线程组,否则如果处理方式是SIG_IGN则忽略 • SIG_DFL 判断信号的类型,有些信号的默认操作有忽略、停止线程组、终止线程组。
用户指定的处理函数 • sigframe • 保存trapframe阻塞信号 • 参数和返回地址 • 神奇的数字movl $400, %eax; int $0x80 • 修改eipesp • sigreturn系统调用 恢复
test1 • 产生信号给一个进程 • 测试sigaction() sigprocmask() tkill() • 信号处理函数的调用(包括在自定义信号处理函数中使用系统调用) • SIGKILL终止进程
test2 • 产生信号给一个线程组 • 测试sigsuspend()kill_bionic() • SIGSTOP停止线程组 • SIGKILL终止线程组
动态链接——原理 • 内核态准备工作: • 通过可执行文件是否包含PT_INTERP“段”,判断其为动态链接或静态链接 • 将可执行文件的PT_LOAD的“段”都载入内存 • 若为动态链接,可执行文件必包含PT_INTERP“段”,该“段”的内容为动态链接器的路径。将动态链接器的PT_LOAD“段”都载入内存 • 动态链接器和可执行程序本身的装载地址就是链接地址,无需重定位
动态链接——原理 • 内核态准备工作: • 若为动态链接,将eip置为动态链接器的入口;否则,将eip置为可执行文件的入口 • 设置初始堆栈。若为动态链接,堆栈中需要有一些动态链接器需要的信息。Android的动态链接器叫做linker,其源码中体现出只需要三项: • AT_PHDR:program header在内存中的地址 • AT_PHNUM:program header个数 • AT_ENTRY:可执行文件的入口 • glibc需要的更多此类信息
动态链接——原理 • 用户态: • 若为动态链接,先执行的将是动态链接器。 • 动态链接器根据内核在堆栈中放置的辅助信息,找到PT_DYNAMIC“段”。这个段中存放着动态链接器需要的一切信息 • 例如,该段中类型为DT_NEED的项表示该可执行程序依赖的动态链接库的名称在strtab段中的序号,由此动态链接器可以得到需要加载的动态链接库的名称
动态链接——原理 • 用户态: • 动态链接库的链接地址一般从0x0开始,由动态链接器选择可用地址作为基地址后加载。 • 加载完动态链接库之后,需要对动态链接库的每一个重定位项(包括变量和函数)进行重定位,具体机制可参见《程序员的自我修养——链接、装载与库》
mmap file • 动态链接器加载动态链接库是通过mmap文件的方法,而ucore的mmap是在文件系统出现以前就有的,从而没有mmap文件的功能 • 我们的做法:如果mmap传进了fd参数,就在mmap的末尾把文件相应的部分直接读入内存,并在vma中存储文件的相关信息;在munmap时把内存写回文件。
mmap file失败的尝试 • 我们尝试如下实现mmap文件: • mmap文件时,给相应vma加入文件信息(struct file指针),不立即载入内存 • 发生缺页中断时,检查vma是否是mmap文件的vma。如果是,将文件读取一页载入相应内存 • 更进一步地,实现共享文件的映射: • 在struct file中,加入被mmap的信息,包括:被mmap起始偏移、长度、起始物理内存地址 • 第一次缺页时,新申请一页空间,将文件内容载入页中,并将mmap信息设置好 • 以后再缺页时,如果是同一块被mmap的区域,直接将页表项改为已被mmap到的物理地址
mmap anonymous • 另外,动态链接器还有mmap带有标志MAP_ANONYMOUS的行为 • 我们对它的理解:它用来预留一块虚存地址空间,以后可以在这块预留空间上进一步mmap,这是其他mmap不允许的 • 因此,给vma加上ANONYMOUS标志位。如果碰到这样的情况,先分配vma;进一步分配时,将这个vma分裂即可。
Dalvikvm失败的尝试 • 第九周、第十周精力主要在尝试运行dalvikvm上 • 成功的在Android的ARM模拟器上运行dalvikvm,并跑出Hello world程序 • 未能在x86 Linux上运行成功。初期加载动态链接库等阶段都已完成,是在dalvikvm开始初始化的时候出错的。错误信息是无法加载某个国际化语言模块。Google很久,无果。
glibc失败的尝试 • 一度bionic libc动态链接不能成功,尝试跑出glibc的动态链接(现在看来很荒谬)。 • 成功编译出glibc,在Linux上用编译出的glibc替代本机的glibc,能够正常运行。 • 在ucore上始终提示assert failed,想修改glibc源码使之多输出一些调试信息(例如__FILE__, __LINE__),竟然未能成功。 • glibc比bionic libc复杂得多需要更多的syscall,更多的动态链接信息,还有很多不太了解的编译参数,最终作罢。