前言

MIT6.828操作系统工程Lab1-Booting a PC实验报告中,出现了高版本gcc(> 7.1.1-3)编译出的JOS内核不能使用的问题(Triple Fault)。此外,gcc无法编译生成32位的JOS内核。因此需要手动降级到低版本并且开启gcc-multilib。

根据arch-dev-public,gcc-multilib已经被合并至了core/gcc之中。当然仍然可以通过Arch Linux Archive获取旧版本的gcc-multilib。

然而Arch是一个崇尚“Modernity”的发行版(参考Arch之道):

Arch尽全力保持软件处于最新的稳定版本,只要不出现系统软件包破损,都尽量用最新版本。

Arch Linux strives to maintain the latest stable release versions of its software as long as systemic package breakage can be reasonably avoided.

综合arch-dev-public对于gcc所做的改动和Arch之道,保持系统gcc为旧版本的gcc-multilib显然不是一个“优雅”的做法。最终,我决定按照6.828 Lab Tools的指导,手动编译gcc 4.6.1作为JOS内核的交叉编译工具链。

问题

编译gcc 4.6.1中遇到的问题以及解决方案总结如下:

  1. 网站上给出的部分软件包的下载链接已经过期。
  2. 网站上的教程默认将工具链安装在了/usr/local/下,这里为了管理上的方便没有采用默认的做法,而是将gcc直接安装在了home目录下(通过configure时的–prefix参数指定)。
  3. 接2,使用./comfigure –help获取帮助。在某些包configure时,需要使用如–with-mpc=… –with-gmp=…等参数来告知相应包的位置。
  4. 由于texinfo版本过高导致低版本的gcc不能正确编译,需要在configure时加上MAKEINFO=missing(当然,你也可以降级texinfo)。
  5. gcc 4.6.1的源码存在一个作用域错误问题。找到gcc/gengtype.c的write_field_root函数,将struct pair new;移动到函数的起始位置。
  6. 编译gcc时出现了libmpc.so.2库找不到的问题。需要修改环境变量LD_LIBRARY_PATH并添加相应的目录,或者也可以在configure的时候用参数–libdir指定库位置。
  7. gcc 4.6.1的源码存在一个数组访问越界错误,具体参考这里

Modules

包管理软件与环境变量

提一个简单的问题,当我们使用包管理软件(例如pacman)安装某个包(例如unzip)之后,在终端中输入unzip …即可解压zip文件。那么包管理软件究竟做了什么呢?

如果是从yaourt安装AUR的包,情况又会发生什么变化呢?

包管理软件主要管理各种包以及处理包与包之间的依赖问题。在安装包的时候,包管理软件从镜像源获取包,将包解压并且将内容复制到系统对应的位置。
诸如yaourt这样的包管理软件使用ABS(Arch Build System),根据pkgbuild文件的指导在本机下载源码、编译并打包,并且将生成的包安装到系统中。

以unzip-natspec为例(这是一个可以识别zip编码格式的unzip版本),我们使用pacman查看包的内容以及对应的位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pacman -Ql unzip-natspec                       
unzip-natspec /usr/
unzip-natspec /usr/bin/
unzip-natspec /usr/bin/funzip
unzip-natspec /usr/bin/unzip
unzip-natspec /usr/bin/unzipsfx
unzip-natspec /usr/bin/zipgrep
unzip-natspec /usr/bin/zipinfo
unzip-natspec /usr/share/
unzip-natspec /usr/share/licenses/
unzip-natspec /usr/share/licenses/unzip/
unzip-natspec /usr/share/licenses/unzip/LICENSE
unzip-natspec /usr/share/man/
unzip-natspec /usr/share/man/man1/
unzip-natspec /usr/share/man/man1/funzip.1.gz
unzip-natspec /usr/share/man/man1/unzip.1.gz
unzip-natspec /usr/share/man/man1/unzipsfx.1.gz
unzip-natspec /usr/share/man/man1/zipgrep.1.g

可以看出unzip被放在/usr/bin目录下。可是为什么我们可以直接在终端中输入unzip …执行解压的命令而不是输入/usr/bin/unzip …呢?这就涉及到了环境变量PATH。

环境变量是一个动态命名的值,用来影响计算机上进程运行的行为。PATH设置了一组包含可执行文件的目录。在输入unzip的时候,终端默认会在PATH所包含的目录中寻找unzip,如果找到了就执行它。我们使用echo命令打印当前终端的环境变量。

1
2
echo $PATH
/home/user/intel/vtune_amplifier_2018.1.0.535340/bin64 /bin /usr/bin /usr/local/bin /usr/local/sbin /usr/lib/jvm/default/bin /usr/bin/site_perl /usr/bin/vendor_perl /usr/bin/core_perl /usr/lib/smlnj/bin /usr/bin/site_perl /usr/bin/vendor_perl /usr/bin/core_perl

6.828网站上默认将工具链安装在了/usr/local目录下,而/usr/local/bin是位于环境变量PATH中的。
因此,我们的qemu以及make可以直接找到编译好的工具链并调用。
现在,我们将工具链安装在了一个不属于PATH的目录中。对于这种情况,我们必须使用某种方式(如shell脚本),将相应目录添加PATH中。

对于我们编译的工具链,还需要设置环境变量LD_LIBRARY_PATH才可正常工作。LD_LIBRARY_PATH涉及linux的动态链接库(共享库),可以自行去搜索相关知识。

当我们安装的程序变多,脚本也会越变越多,管理诸如PATH的环境变量也变得不方便。
我们的jos内核使用的工具链加上了“i386-jos-elf”的前缀。若没有该前缀,添加环境变量后,终端会优先使用gcc 4.6.1作为默认的gcc,这显然不是我们期望的结果。
因此,我们可能还需要写另一个脚本,将相应的目录从PATH中移除。

此外,根据脚本的是否在终端启动时被执行,还可能出现不同终端环境不同的问题。

那么,有没有一种方法可以快速方便的管理不同软件以及他们所需要的环境变量呢?Modules正是一个可行的方法。

简介

Modules(Environment Modules)通过modulefile动态地改变用户环境。可以在Arch中安装env-modules(AUR)以使用Modules。

安装完毕后,需要编辑终端的配置文件来启用Modules。Modules的shell初始化放在/etc/modules/init文件夹下。

Modules使用Tcl作为脚本语言,编写modulefile时可以直接man modulefile获取帮助。

实例

给出管理jos工具链的modulefile,文件放置在/etc/modules/modulefiles文件夹下。使用module load jos加载环境,module unload jos取消环境。modulefile中的prepend-path扩展了环境变量PATH以及LD_LIBRARY_PATH。在调用unload的时候prepeng-path会自动被替换成remove-path。

jos

1
2
3
4
5
6
7
8
9
10
11
#%Module1.0
##
## modulefile
##
proc ModulesHelp {} {
puts stderr "Adds cross compile toolchain(gcc 4.6.1 i386) for MIT jos to user environment"
}
module-whatis "adds cross compile toolchain(gcc 4.6.1 i386) for MIT jos to user environment"
set root_dir /home/chenzhihao/6.828/gcc
prepend-path PATH $root_dir/bin
prepend-path LD_LIBRARY_PATH $root_dir/lib

评论和共享

最近开始阅读Structure of Interpretation of Computer Science即计算机程序的构造与解释这本书。该书中使用的是Scheme作为Lisp的方言。
在此之前华盛顿大学的《程序设计语言》这门课的PartB中,我使用的Lisp方言是Racket,配合DrRacket作为IDE使用。因为最近一直在配置vim,这一次打算使用vim作为主力开发环境。

如果你对我的配置文件(vim,tmux等)感兴趣,可以看这里

效果

最终的效果如下图所示:

思路

最初的思路来自于网上的一篇文章,作者也是正在阅读SICP这本书,其提到了使用tmux进行分屏,在一边使用vim进行代码的编辑,另一边运行mit-scheme作为REPL(Read-Eval-Print-Loop)解释scheme代码。

但是如果这么做的话,需要反复执行mit-scheme < source.scm,这样做显然不能提高我们编程的效率。

这几天逛友链博客的时候发现刚好Kyler也在读SICP并且配置了vim环境,具体可以参考这篇博客
受到这篇博客的启发,我决定使用vim-slime来“沟通”vim和tmux,将vim中的Scheme代码发送到运行在tmux中的解释器,并且运行给出结果。

在学习tmux的过程中我接触到了tmuxinator这个tmux配置管理工具。可以通过tmuxinator直接配置tmux session,十分方便。

要求

这篇博客的配置依赖于:

  1. vim(文本编辑器)
  2. tmux和tmuxinator(终端会话工具)
  3. mit-scheme(Scheme解释器)

实现

首先在家目录下的.tmuxinator子文件夹下创建新的配置文件scheme.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# tmuxinator configuration file for scheme
# location: ~/.tmuxinator/scheme.yml

name: scheme
root: .

startup_window: main
socket_name: tmux_scheme

windows:
- main:
layout: even-horizontal
panes:
- vim <%= @args[0] %>
- mit-scheme

该配置文件指定新的scheme session拥有一个窗口main,该窗口有两个窗格,一个执行vim,另一个执行mit-scheme,并且创建的session的socket name为tmux_scheme。

然后我们在.vimrc中安装并配置vim-slime插件。
这里我直接使用插件管理器Vundle安装vim-slime:

1
Bundle 'jpalardy/vim-slime'

然后在.vimrc中加入配置:

1
2
3
4
5
6
7
8
9
" vim-slime configuration
" 设置目标为tmux
let g:slime_target = "tmux"
" 为tmux设置默认配置,指定socket_name为tmux_scheme,目标窗格为当前窗口的第2个窗格
let g:slime_default_config = {"socket_name": "tmux_scheme", "target_pane":":.1"}
" 指定slime在第一次发送代码时不要询问配置
let g:slime_dont_ask_default = 1
" 指定作为缓冲区的文件(该文件默认在执行完后不会被清空或者删除)
let g:slime_paste_file = "$HOME/.slime_paste"

vim-slime的默认快捷键是 (两次Ctrl-C)发送代码; v(Ctrl-C + v)修改配置。

最后编写测试文件test.scm,执行tmuxinator start scheme test.scm,在vim中按下两次Ctrl-C。应该能实现和上文效果图一样的效果。

其他

本篇博客只是给出了大概的环境配置的思路以及一些具体的配置文件。
如果想要深度地定制属于自己的Scheme开发环境,你可能还需要阅读vim、vim-slime、tmux、tmuxinator的文档与网上的教程。
配置的方式也不仅仅局限于tmux,gnu-screen、vim-terminal等同样能作为vim-slime连接的目标。
配置的环境也不必仅仅局限于Scheme,python、sml等解释型语言均可使用。

评论和共享

  • 第 1 页 共 1 页
作者的图片

码龙黑曜

iOS开发者/计算机科学/兽人控


华中科技大学 本科在读


Wuhan, China