1.引言
本文着重讲述如何制作基于linux的usb启动盘,此usb启动盘能够实现以下功能。 a. usb启动盘的Linux内核usblinux.kernel应支持尽可能多的硬件(包括硬盘驱动,网卡驱动,usb驱动) b. u盘根文件系统放在ramdisk中,启动之后解压到ram中。 c. usb启动盘启动之后能够在目标机上执行分区(fdisk),格式化(mkfs.*)以及在各种块设备上安装grub d. usb启动盘能够临时充当局域网内的网关。 e. usb启动盘能够在一台机器上快速的搭建网关服务器。 2.定制操作系统所要考虑的事情 不管是在u盘上面还是在其他存储设备上(硬盘,软盘)安装Linux,我们所需要考虑的事情基本一样。 1)定制自己的内核(包括选定确当的内核版本,内核是否支持模块等等) 2) 定制自己的根文件系统(包括根文件系统的架构,根文件系统的内容,根文件系统所采用的文件系统类型等等) 3) 配备引导加载程序(bootloader) 3. UsbLinux 的系统架构 结合1和2我们现在来具体定制我们的UsbLinux。 1) 内核选用 2) 内核支持模块加载功能,上面说了我们的内核应该尽可能支持多的硬件,如果我们把这些硬件的驱动全部静态编译进内核的话,内核将会变的非常庞大,这是我们不希望看到的,这不仅仅会减慢Usblinux启动的速度,而且我们不能动态的去除不必要的内核模块。 3) 根文件系统选择ext2文件系统,前面说了我们的根文件系统是放在ramdisk中的,ramdisk正如其名,存在于ram中并且功能犹如块设备。因此也就决定了根文件系统里面的东西修改之后断电不能保存,ext2文件系统的特性主要包括可读写,可压缩,不具备断电可靠性,ext3,reiserfs等日志文件系统是具备断电可靠性的,它们用在硬磁盘上比较合适。我们这里的根文件系统存放在ram中,因此选用ext2文件系统比较合适。 4) bootloader 选择grub,我们不是在搞嵌入式所以grub是一个非常好的选择。
说明:下面的所有操作都是在red hat linux 9里面完成的。 4. 编译安装内核 4.1. 选择配置内核方式
配置内核有多种方法,切换到源码目录下, a. make oldconfig,make config(注,除非你很喜欢这种配置方式,否则建议你还是不要使用它们,它们不但不直观,而且配置容易出错) b.make menuconfig,这是最常用的一种配置方法,方便直观。如果没有其它特殊原因,建议你使用这种配置方法。 c.make xconfig, 这个需要X窗口的支持,如果你喜欢这个配置方法也很方便 4.2. 内核配置 这当然是很重要的一个步骤了,关于配置内核的具体细节网上有很多这样的文章,这里只重点说以下几个重要的地方。 Loadable module support: 这个我们支持,因为我们的驱动大多是编译成模块方式的。 Process type: 由于我们的UsbLinux是做成通用的而不是针对某个目标机的,所以处理器的选择至关重要,为什么这样说呢? 如果你的内核选择的处理器类型是的Athlon/Duron ,那么在一台386或者586的机器上到解压内核的时候可能会出问题,我试了在奔腾或者赛杨的处理器上也是到解压内核的时候就停在那边了。我们现在的处理器大多是X86系列的,因此处理器选择386,可以避免上述的麻烦,当然针对具体的机器这个必然会对机器的性能产生影响。 Block devices: 一定要选择ram disk support 和initrd support NetWorking options: 这里面的是关于内核支持的网络功能,我们的UsbLinux需要临时充当网关,而且能够利用它搭建网关服务器,因此这里面的大多数选项都要选,这里面的选项比较多,最好对网络方面的知识有个大概的了解。 ATA/IDE support: 硬盘的类型,这个里面也有比较多的硬盘型号,我们现在大多是IDE硬盘,但是如果你想让你的UsbLinux的通用性好一些的话,最好上网查查硬盘厂商的相关资料。 SCSI support: 尽管现在很少见到SCSI的硬盘,不过我们还不得不让内核支持SCSI,为什么呢? linux把usb设备是当成SCSI看待的,因要支持USB就要支持SCSI才行。具体的把下面这两个选项编译进内核就可以了,其它的基本上用不着。 <*>SCSI Support <*>SCSI Disk Support 另外为了在vmware上面测试,我把BusLogic这个驱动编译进了内核。 NetWork Devices Support: 网络设备支持,我们基本上只需要选择Ethernet(10 or 100Mbit)这里面的选项就可以了,其它的千M以太网卡,FDDI以及PPP之类的一般用的不多,当然还是要视具体情况而定。在 Charater Devices: 字符设备支持,把Virtual terminal,support console on virtual terminal, Standard/generitic serial support和Support for console on serial port这两项编译进内核就差不多了。 File Systems: 文件系统支持,由于我们使用的ext2文件系统,所以把Second extended fs support 这一项编译进内核。另外/proc文件系统也编译进内核,因为proc文件系统很有用。其余的ext3,fat,Vfat,ntfs 给M上吧,另外吧网络文件系统nfs也M上吧,Partion types这里面的选择PC BIOS就可以了,Native Language Support把codepage 437,936以及nls iso8859-1编译进内核,其它的就不用选了。 USB Support: Usb设备支持,把UHCI和OHCI support给M上,另外USB Mass Storage Suppor 一定要选上,把它编译进内核或者M上都可以。最后吧USB Device filesystems 给编译进内核,这会在/proc文件系统里面生成相应的选项,便于调试用。其它的声卡,红外线之类的设备都可以不选。 这里没有涉及的选项都是不常用的。 内核配置完毕,保存退出。 4.3. 建立依赖关系 这一步一个命令make dep就可以搞定。内核源码树中大多数文件都会与一些头文件有依赖关系,make dep期间会在内核源码树中每个子目录下面产生一个隐藏的.depend文件。此文件内包含子目录里面各文件所依赖的头文件清单。 4.4. 建立内核 make bzImage or make zImage 注意bzImage和在zImage都是经gzip算法压缩过的内核映像,所不同的是在zImage的大小无法超过500KB,而bzImage则没有这个限制,如果映象建立过程中有错误,一个就是你的内核配置不正确,还有一个可能就是需要make mrproper来消除上一次的编译记录,make mrproper之后内核源码相当于刚安装时候的状态。 4.5. 建立和安装模块 make modules & make modules_install 注意模块的默认安装位置是/lib/modules目录下,目录名即是你的内核版号,如果/lib/modules目录下有一个相同的内核版本号目录,如果很重要的话你最好把它备份,因为会被新的覆盖。至此内核以及相应的模块已经建立好了。 5.建立根文件系统 5.1.根文件系统的基本结构 首先建立一个roofs文件夹用来存放根文件系统的内容。 #mkdir /mnt/rootfs 根文件系统的顶层目录: (摘自<<构建嵌入式LINUX系统>> page 179)
5.2. 建立UsbLinux根文件系统的基本结构 对于u盘上的linux或者做网关的linux来说,用户的权限问题并不复杂,我们只需要设置一个超级用户以及其它系统服务必要的帐户即可(比如ftp,sshd等等),由于相当于是单用户,因此home目录也可以不要,其它的目录在我看来都是必要的,下面我们开始建立根文件系统的基本结构。 #cd /mnt/rootfs #mkdir bin sbin dev etc lib root tmp usr var proc opt mnt #mkdir usr/{bin,sbin ,lib,share} #mkdir var/{lock,log,run,empty} 一般二进制命令可以存放的目录包括bin,sbin,usr/bin,usr/sbin,它们有什么区别呢? 二进制文件放在哪个目录,这与它在系统中所扮演的角色密切相关,如果这是普通用户和系统管理员必备的二进制文件(比如ls和mkdir等),就会放在bin目录下,如果只是系统管理员所具有的一些特权指令(普通用户使用这些指令有限制,如ifconfig),那么它应该放在sbin目录下,usr/bin目录下存放的是普通用户和管理员"不常用"的命令,而usr/sbin目录下是管理员"不常用"的命令。 上面已经说过我们不需要区分普通用户和系统管理员(root),因此我们只需创建一个系统管理员账号就可以了,但是这里为什么我们还要创建这四个目录呢? 是因为我们用到busybox套件,下面说明安装定制应用程序的时候会讲到。 下面我们把刚才建立的内核模块拷贝到lib目录下(注:以后我们所有的操作都是以/mnt/rootfs为根操作的) #mkdir lib/modules #cp –dpR /lib/modules/ 5.3. 选择链接库 链接库是文件系统中一个非常重要的部分, 它也是整个根文件系统中最耗空间的一个部分。链接库是应用程序执行期间必不可少的一部分,当然如果你编译应用程序的时候库都是静态编译进去的,那么就不需要额外麻烦的自己建立链接库了,比如嵌入式系统中的uClinux,不过这个做法的缺点就是会额外的消耗存储空间。我们这里采用动态链接的方法。目前比较流行的链接库有glibc和uClibc,glibc是gnu的C链接库,一般的linux发型版都是使用的glibc库,而uClibc主要是针对嵌入式系统而定制的库,它的特点就是小。尽管uClibc也可以用在X86机器上,但它并不具有glibc的稳定性,一般在pc机上都是使用的glibc库。我们的目标机是pc机,所以我们选择glibc库,我们可以直接把宿主机上面的库文件拿来用。 5.4 建立设备文件 依照unix的传统,在Linux系统中任何对象(包括设备 )都可以视为文件,在Linux系统中所有的设备文件都放在dev目录下面,建立设备文件通常有两种方法,一个是直接把宿主机上面的拷贝过来(拷贝的时候需要加上dpR参数),另外一种方法就是自己手工用mknod创建(注意这两种方法都需要root权限)。 一般情况下我们都是使用第一种方法,但是这里面为了便于大家更深入的理解设备文件,我使用第二种方法来创建。首先来看如何使用mknod命令来创建特定的设备文件。 #cd dev #mknod -m 666 null c 1 3 上面这条命令创建了null设备,-m 参数指定所创建设备的基本权限,null是设备名称,c代表是字符设备,相应的块设备用b表示,1是主编号,3是次编号。 关于设备的主次编号的权威信息的来源可以查看内核源码树中的 Documentation/devices.txt 我们这里需要建立的dev条目如表格所示。
下面我们写一个sh脚本mkdev.sh来创建这些设备,脚本内容如下: #!/bin/sh rm -rf mem console urandom null zero tty* ptyp* ram* hda* hdb* \ sda* sdb* fd* hdc cdrom mknod -m 600 mem c 1 1 mknod -m 600 console c 5 1 mknod -m 644 urandom c 1 8 mknod -m 666 null c 1 3 mknod -m 666 zero c 1 5
mknod -m 666 tty c 5 0 for i in 0 1 2 3 4 do mknod -m 600 tty$i c 4 $i mknod -m 660 ttyp$i c 3 $i done for i in 0 1 2 3 4 do mknod -m 660 ptyp$i c 2 $i done
mknod -m 640 ram0 b 1 0 mknod -m 640 ram1 b 1 1
mknod -m 660 hda b 3 0 mknod -m 660 hdb b 3 64 mknod -m 660 hdc b 22 0 ln -s hdc cdrom for i in 1 2 3 4 5 6 7 8 9 10 do mknod -m 660 hda$i b 3 $i mknod -m 660 hdbd$i b 3 `expr 64 + $i` done
mknod -m 660 sda b 8 0 mknod -m 660 sdb b 8 16 for i in 1 2 3 4 5 do mknod -m 660 sda$i b 8 $i mknod -m 660 sdb$i b 8 `expr 16 + $i` done mknod -m 640 fd0 b 2 0 #end #./mkdev.sh 5.5. 定制应用程序。 这个是个大头,但是由于一般的发行版自带的应用程序都很庞大,而且应用程序依赖的动态链接库也大的出奇,所以就有人想出很多办法,其中busybox在这方面做的非常的好,目前busybox在嵌入式方面应用非常广泛,BusyBox真如其名,它把所有的应用程序集成在一个命令里面,目前BusyBox支持unix/linux命令集里面的大部分命令,包括基本的命令cp,cd,chmod,chown,date,cat,ar等,编辑工具vi,awk等,网络工具ifconfig,netstat,route,telnet,ftpget,ftpput,telnetd等,模块工具lsmod,insmod,rmmod,modprobe,压缩解压缩工具gzip,gunzip,tar,bzip,bunzip,zip,unzip等,查找工具find,grep,xargs,init工具init,poweroff,halt,reboot,账号密码管理工具addgroup,delgroup,adduser,deluser,getty,login,shadow,进程相关工具free,kill,killall,top,ps等,其它的last,fdisk,dmesg,swapon,swapoff等等都有,另外,BusyBox还提供几个shell,我们一般用ash,它和bash非常的相似。 下面说说如何使用busybox定制自己的应用程序。 首先到网上下载一个稳定的BusyBox版本,我用的版本是busybox- #make menuconfig (不知道的选项看看help就可以了) #make #make install 注意busybox默认是安装到根目录下的_install文件文件夹。 #cd _install #ls 你会发现bin目录下只有一个busybox二进制文件,其它的命令,bin,sbin以及 usr/bin,usr/sbin目录下的都是指向busybox的链接文件。牛逼吧,这么多命令一个应用程序就搞定了,我们看看busybox有多大 #du -k bin/busybox 420 这是我定制出来的busybox的大小,够小的吧。 在busybox的根目录下编译好二进制程序之后。 #make install PREFIX=/mnt/roofs 这样,应用程序就会安装到指定位置,切换到/mnt/rootfs下看看,bin目录,sbin目录,usr/bin目录,usr/sbin目录下是不是出现了很多的应用程序。 busybox安装好之后,看看它依赖的动态链接库 #ldd busybox libm.so.6 => /lib/tls/libm.so.6 (0×40026000) libcrypt.so.1 => /lib/libcrypt.so.1 (0×40048000) libc.so.6 => /lib/tls/libc.so.6 (0×42000000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0×40000000) #cd /mnt/rootfs #cd lib #cp -dpR /lib/libc- #cp -dpR /lib/libc.so.6 ./ #cp -dpR /lib/libm* ./ #cp -dpR /lib/libcrypt- #cp -dpR /lib/libcrypt.so.1 ./ #mkdir tls #ln -s libc- #ln -s libm- 除了基本的命令之外,BusyBox还支持init功能,如同其它的init一样,busybox的init也是完成系统的初始化工作,关机前的工作等等,我们知道在Linux的内核被载入之后,机器就把控制权转交给内核,linux的内核启动之后,做了一些工作,然后找到根文件系统里面的init程序,并执行它,BusyBox的init进程会依次进行以下工作:(参考<<构建嵌入式LINUX系统>> p201) 1. 为init设置信号处理过程 2. 初始化控制台 3. 剖析/etc/inittab文件 4. 执行系统初始化命令行,缺省情况下会使用/etc/init.d/rcS 5. 执行所有导致init暂停的inittab命令(动作类型:wait) 6. 执行所有仅执行一次的inittab(动作类型:once) 一旦完成以上工作,init进程便会循环执行以下进程: 1. 执行所有终止时必须重新启动的inittab命令(动作类型:once) 2. 执行所有终止时必须重新启动但启动前必须询问用户的inittab命令(动作类型:askfirst) 初始化控制台之后,BusyBox会检查/etc/inittab文件是否存在,如果此文件不存在,BusyBox会使用缺省的inittab配置,它主要为系统重引导,系统挂起以及init重启动设置缺省的动作,此外它还会为四个虚拟控制台(tty1到tty4)设置启动shell的动作。如果未建立这些设备文件,BusyBox会报错。 inittab文件中每一行的格式如下所示:(busybox的根目录下的example文件夹下有详尽的inittab文件范例) id:runlevel:action:process 尽管此格式与传统的Sytem V init类似,但是,id在BusyBox的init中具有不同的意义。对BusyBox而言,id用来指定启动进程的控制tty。如果所启动的进程并不是可以交互的shell,例如BusyBox的sh(ash),应该会有个控制tty,如果控制tty不存在,Busybox的sh会报错。BusyBox将会完全忽略runlevel字段,所以空着它就行了,你也许会问既然没用保留着它干吗,我想大概是为了和传统的Sytem V init保持一致的格式吧。process字段用来指定所执行程式的路径,包括命令行选项。action字段用来指定下面表中8个可应用到process的动作之一。
以下是我的usblinux的inittab文件 ::sysinit:/etc/init.d/rcS ::respawn:/sbin/getty 38400 tty1 tty2::askfirst:-/bin/sh tty3::askfirst:-/bin/sh ::restart:/sbin/init ::ctrlaltdel:/bin/umount -a -r 这个inittab执行下列动作 1. 将/etc/init.d/rcS设置成系统的初始化文件 2. 在38400 bps的虚拟终端tty1上启动一个登陆会话 (注意getty的用法) 3. 在虚拟终端tty2和tty3上启动askfirst动作的shell 4. 如果init重新启动,将/sbin/init设置成它会执行的程序 5. 告诉init,在系统关机的时候执行umount命令卸载所有文件系统,并且在卸载失败时用只读模式冲新安装以保护文件系统。 5.6 系统的配置文件 配置文件一般放在/etc/目录下, #!/bin/sh /bin/mount -n -t proc /proc /proc /bin/mount -n -o remount,rw / /bin/mount -av /bin/hostname usblinux /etc/init.d/rc.nic /etc/init.d/rc.network /etc/init.d/rc.usb /etc/init.d/rc.local 1. 安装proc文件系统,但是不写入/etc/mtab文件(mtab文件是df命令读取的文件) 2. 以读写模式重新挂载根文件系统(需要/etc/fstab文件) 3. mount -av 挂载所有在/etc/fstab里面列出的文件系统 4. 主机名命名为usblinux 5. 依次执行rc.nic(自动检测网卡),rc.network(配置网络),rc.usb(加载usb模块),rc.local(执行本地的一些服务) 上面使用mount命令的时候已经涉及到了fstab文件,下面是我的fstab文件 /dev/ram0 / ext2 defaults 0 0 proc /proc proc defaults 0 0 关于fstab配置文件的语法可以man fstab 其实rcS里面的mount -av没有必要加上去。 这个文件里面的内容是动态变化的,当mount一个文件系统的时候,如果mount 没有加上-n参数,那么安装信息就会写入mtab文件,df命令读取的就是这个文件。 这个文件是sh用的,当用户获得一个shell后,sh就会根据这个文件配置用户的登陆环境,下面是我的profile文件。 PATH=/bin:/sbin:/usr/bin:/usr/sbin PS1=’[\u@\h \W]\$ ‘ HOSTNAME=’/bin/hostname’ export PATH HOSTNAME PS1 alias l.=’ls -d .[a-zA-Z]* –color=tty’ alias rm=’rm -i’ alias cp=’cp -i’ alias mv=’mv -i’ export PATH=/usr/local/bin:$PATH export PATH=/usr/local/sbin:$PATH
其中PATH环境变量指定当用户键入一个命令时,sh寻找这个命令的路径。 PS1指定sh提示符的格式 其它的export命令,alias命令不用说了吧,busybox里面的ash和bash非常相似,因此只要熟悉bash就会不成问题。 这两个文件是与lib库有关的,它们用来指定应用程序寻找lib库的路径。 我的ld.so.conf文件内容如下。 /lib /usr/lib /usr/local/lib ld.so.cache里面的内容是由是ldconfig命令生成的,ldconfig根据ld.so.conf配置文件生成ld.so.cache里面的内容,应用程序读取的是ld.so.cache文件,因此如果变动了ld.so.conf文件,需要运行一次ldconfig才能是新的配置生效,另外如果新加了lib库文件,也需要ldconfig。 group里面存放的是用户组的信息。 我的group文件内容如下: root:x:0:root ftp:x:50: passwd里面存放的是用户的信息 我的passwd文件内容如下: root:x:0:0:root:/root:/bin/sh ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin sshd:x:74:74:piviledge-separated SSH:/var/empty/sshd:/sbin/nologin 注意,group和passwd里面的每一行都不是多余的。 shadow文件是pam认证用到的密码存放文件 网络应用程序用到的标准服务端口映射表 我的services文件如下: tcpmux 1/tcp tcpmux 1/udp ftp-data 20/tcp ftp 21/tcp ssh 22/tcp ssh 22/udp telnet 23/tcp nameserver 42/tcp name syslog 514/udp auth 113/tcp authentication tap ident 因为我需要开启sshd,syslogd服务,同时会使用ftp,ssh,telnet应用程序。所以就会加上上面的东西,但是注意并不是所有的网络应用程序都会依赖与services文件,比如telnet,但是也许telnetd需要。 这个文件的作用很大,没有它很多程序都不能正常运行,关于它的具体作用还是用man nsswitch.conf看看吧。 我的nsswitch.conf文件内容如下: passwd: files shadow: files group: files hosts: files dns ethers: files netmasks: files networks: files protocols: files services: files netgroup: files publickey: files automount: files aliases: files 注意hosts这一行一定要加上dns,不然即使有resolv.conf文件,域名也不能解析,注意nsswitch.conf是对应一组lib库的,这里我们用了files,dns,因此需要拷贝相应的lib库到/lib目录下. #cd /mnt/rootfs #cp -dpR /lib/libnss_files* ./lib #cp -dpR /lib/libnss_dns* ./lib 我的hosts文件内容如下,不用说也应该清楚什么意思了,不清楚的man hosts 127.0.0.1 localhost:localdomain localhost host.conf和resolv.conf是域名解析的时候用到的配置文件,其中resolv.conf里面是域名服务器的ip地址,host.conf里面放的是主机的查找规则。不了解的还是man host.conf和man resolv.conf 我的host.conf与resolv.conf文件内容分别如下: #cat /mnt/rootfs/etc/host.conf order hosts,bind #cat /mnt/rootfs/etc/resolv.conf nameserver 202.112.20.131 syslogd服务程序读取的配置文件。 我的文件内容如下: # Log all kernel messages to the console. # Logging much else clutters up the screen. #kern.* /dev/console
# Log anything (except mail) of level info or higher. # Don’t log private authentication messages! *.info;mail.none;authpriv.none;cron.none /var/log/messages
# The authpriv file has restricted access. authpriv.* /var/log/secure
# Save boot messages also to boot.log local7.* /var/log/boot.log
5.7 系统初始化的脚本 上面说到rcS文件时,涉及到了/etc/init.d/rc.nic,/etc/init.d/rc.network,/etc/init.d/rc.usb,/etc/init.d/rc.local四个初始化脚本文件。下面来说说它们的作用。 此文件用来自动加载网卡对应的模块(驱动),并且删除没有用到的网卡驱动模块。 #!/bin/sh # # detect network cards shell # dmesg -n 1 #prevent the messages from appearing on the console ERR_DEV=/dev/null # redirect verbose messages NetcardPath=/lib/modules/ loaded=4 #the kernel already detected card,you may want to change it #according to file `/proc/net/dev` # invoked by the init script or by the user? [ "`basename $0`" = "rc.nic" ] && MODE=init || MODE=user
# number of NICs already detected NICs=$((`cat /proc/net/dev | wc -l` -$loaded))
# when init, skip auto probing if we already found some NIC(s) if [ $NICs -ge 1 -a "$MODE" = "init" ]; then echo "Found $NICs network card(s). Skip auto probing …" exit 0 fi
# official NIC modules NET_MODs=`find /lib/modules/\`uname -r\`/kernel/drivers/net/ \ -type f -printf "%f\n" 2>$ERR_DEV \ | cut -d "." -f1` # save the original dmesg messages first [ -e /var/log/dmesg ] || dmesg > /var/log/dmesg
echo "Probing network cards … " CUR_NICs=$NICs for i in $NET_MODs ; do ORG_MODs=`cat /proc/modules | wc -l` ORG_NICs=$CUR_NICs
echo -en "\r\t$i \t" #if modprobe fail we think that this module is not needed #so we delete it to save the space if ! modprobe $i 1>$ERR_DEV 2>$ERR_DEV then temp=`find $NetcardPath -name $i.o` if [ -f $temp ] then rm -rf $temp fi fi
CUR_MODs=`cat /proc/modules | wc -l` CUR_NICs=$((`cat /proc/net/dev | wc -l` -$loaded))
if [ $CUR_NICs -eq $ORG_NICs ]; then if [ $CUR_MODs -gt $ORG_MODs ]; then #it is not a netcard module #so we delete it RMMODs=`head -$(($CUR_MODs-$ORG_MODs)) /proc/modules | cut -d" " -f1` for m in $RMMODs ; do rmmod $m 1>$ERR_DEV 2>$ERR_DEV done fi else echo " ($(($CUR_NICs-$ORG_NICs)))" fi done echo -e "\r \rFound $(($CUR_NICs-$NICs)) card(s), done." rmmod -as exit $(($CUR_NICs-$NICs)) 看懂这个脚本就需要你熟悉shell编程了。由于比较大,我这里不作解释。 这个脚本的内容大部分是从cdlinux上面copy过来的,但是对其进行了修改,其中最大的修改就是添加了删除没有用到的模块的功能,这主要是为了节省空间所用。
这个脚本主要是初始化网络配置, #!/bin/sh #/sbin/ifconfig eth1 218.199.20.98 up /sbin/ifconfig eth0 192.168.0.3 up /sbin/ifconfig lo 127.0.0.1 up route add default gw 192.168.0.1 dev eth0 这个脚本需要根据不同的环境进行修改。接触过linux的人相信这个不难看懂。 加载usb驱动模块,当然如果你的内核是静态编译进usb模块的,那就没有必要在这里多此一举了,不过如果我们要做个网关服务器,还是把它做成动态的模块比较好,因为网关服务器基本上不需要用到u盘,我们大可删除掉usb驱动模块,以节省空间。我们之所以需要usb的驱动模块,不要忘了我们的usbinux是放在u盘上面运行的。当然并不是说要在u盘上面运行linux,一定需要内核支持usb才行,不管是硬盘,u盘,还是软盘都只是个载体,第一章已经说了,我们的文件系统是在ramdisk中的,因此只要内核和文件系统被载入内存,我们就不再需要载体(存储设备)。但是我们很多东西可以放在u盘上面,以节省ramdisk的存储空间,所以还是需要内核识别u盘,等系统启动之后再把需要的东西从u盘拷贝到ram里面。 rc.usb内容如下: #!/bin/sh ## This script is to initilize usb controller and ## The driver module usb-storage ## To use usb under linux the module usbcore,scsi_mod ## and sd_mod are needed,in this ## system they are complied in kernel
#/sbin/usb is the small script to start or stop usb support /sbin/usb start #script rc.usb end 这个脚本里面用到了/sbin/usb这个程序,其实并不是一个真正的程序,它是我写的一个加载和卸载usb模块的shell脚本。/sbin/usb脚本内容如下: #!/bin/sh # #A simple startup script to start usb for linux # case "$1" in start) /sbin/modprobe usbcore /sbin/modprobe usb-uhci /sbin/modprobe usb-storage ;; stop) /sbin/rmmod usbcore /sbin/rmmod usb-uhci /sbin/rmmod usb-storage ;; *) echo "Usage: $0 {start|stop}" exit 1 esac #script /sbin/usb end 我之所以写了这个usb脚本,是因为我发现,启动usblinux后,当机子用过一个u盘时,即使这个u盘umount掉了,再使用另外一个u盘的时候就会出现问题。也许是我的哪边配置有误,不过不管怎么说,这个脚本还是有用的。 注意:虽然上面只列出了,usbcore,usb-uhci,usb-storage三个模块,要能够成功挂载u盘,还需要scsi的支持,也就是需要scsi_mod.o sd_mode.o两个模块,我的内核是把这两个模块静态编译进内核的,因此也就不需要再手动加载了。我之所以把对scsi支持的功能静态编译进内核,是为了测试方便的原故,我测试工具使用的是vmware虚拟机,因为vmware基本上使用scsi硬盘,为了避免添加linuxrc文件,我把vmware的硬盘驱动BusLogic.o静态编译进了内核,所以上面的scsi的两个模块也要编译进内核。 这个shell脚本没有什么好说的,主要是初始化本地的一些服务,比如sshd,syslogd等等 我的rc.local脚本内容如下: #!/bin/sh #/sbin/sshd /sbin/syslogd 在本地启动syslogd服务。 5.8 添加额外的应用程序 虽然busybox支持大多数应用程序,但是往往我们需要的程序它并不支持,这时候我们就只有copy我们的宿主机上的了,第一章说了,我们的usblinux需要能够fdisk存储设备,能够mkfs文件系统,能够使用grub安装grub到指定存储设备。busybox有fdisk,但是mkfs.*在我使用的busybox版本里面还不支持,另外ldconfig这个必须的应用程序busybox里面好像也没有。另外我们也许想在usblinux里面临时使用一下ftp服务器,用来传输文件用,调试的时候strace是必不可少的工具,加进硬盘检测工具smartmontools也是不错的主意,充当网关服务器sshd必不可少吧,虽然可以用其它的比如telnetd等代替,但是它们并没有sshd来的安全,因此我还是选择了sshd。 上面说了这么多的应用程序,如果不采取任何措施直接copy到rootfs里面的话,可想而知,根文件系统的大小会相当大,sshd一个就会净增文件系统 有些应用程序譬如sshd,mkfs*,grub,strace等等,我们平时很少用到,因此如果我们能想出一个办法需要这个应用程序的时候调度出来,不需要的时候把它删掉,那就解决了问题。方法也许有很多,我使用的方法是把这些应用程序及其依赖的库文件压缩,然后把它们的压缩文件安装到opt目录下,需要的时候解压就行了,不需要的时候把解压出来的文件删除掉。我opt目录文件内容如下: #ls opt ldconfig.tar.gz rz.tar.gz strace.tar.gz grub.tar.gz mkfs.tar.gz smartmontools.tar.gz iptables.tar.gz pureftpd.tar.gz sshd.tar.gz 我之所以把ldconfig压缩,是因为ldconfig有536k之大,rz是学校校园上网认证的一个程序,strace是个调试程序,grub,mkfs不用说了吧,smartmontools是硬盘坏道检测工具,iptables做网关服务器所必须的,pureftpd一个很方便的ftp服务器程序,sshd远程登陆工具,包括服务端和客户端的。 下面使用两个脚本来调入和调出/opt下的指定应用程序。 1. 调入脚本expand #!/bin/sh TOOL=$1 if [ ! -f /opt/$TOOL.tar.gz ] then echo "No such tool!" exit fi mkdir /tmp/tool cp /opt/$TOOL.tar.gz /tmp/tool cd /tmp/tool tar zxf $TOOL.tar.gz rm -rf $TOOL.tar.gz cp -dpR /tmp/tool/$TOOL/etc/* /usr/local/etc 2>/dev/null cp -dpR /tmp/tool/$TOOL/sbin/* /usr/local/sbin 2>/dev/null cp -dpR /tmp/tool/$TOOL/bin/* /usr/local/bin 2>/dev/null cp -dpR /tmp/tool/$TOOL/lib/* /usr/local/lib 2>/dev/null rm -rf /tmp/tool echo "done" 2. 调出脚本/sbin/depand #!/bin/sh path=/usr/local case $ "ldconfig") rm -rf $path/sbin/ldconfig echo "done";; "grub") rm -rf $path/sbin/grub echo "done";; "smartmontools") rm -rf $path/sbin/smartctl echo "done";; "strace") rm -rf $path/bin/strace echo "done";; "rz") rm -rf $path/bin/rz rm -rf $path/etc/rz.conf rm -rf $path/lib/libpcap* echo "done";; "mkfs") rm -rf $path/sbin/mkfs* rm -rf $path/lib/libext2* rm -rf $path/lib/libe2p* rm -rf $path/lib/libcom_err* rm -rf $path/lib/libuuid* echo "done";; "pureftpd") rm -rf $path/sbin/*pure* echo "done";; *) echo "this tool is not installed!" esac
譬如要调入grub这个工具,只需要在命令行敲入expand grub即可,使用完grub之后,只要depand grub就可释放grub占有的ram空间。 (注意:expand和depand并不适用与iptables与sshd这两个应用程序,对它们我们需要额外编写脚本,因此expand应该加上对这两个程序的拒绝执行动作,但是我这里没有加,为了安全起见,建议还是应该加上) opt目录下的压缩文件并不是随便做的,我们必须遵循一定的规范,下面我们以制作mkfs工具来说明制作过程。 #cd /mnt/rootfs/opt #mkdir mkfs #cd mkfs #mkdir bin sbin etc lib #cp -dpR /lib/libext2fs.so.2* ./lib #cp -dpR /lib/libe2p.so.2* ./lib #cp -dpR /lib/libuuid.so.1* ./lib #cp -dpR /lib/libcom_err.so.2* ./lib #cp -dpR /sbin/mkfs* ./sbin #cd .. #tar zcf mkfs.tar.gz mkfs #rm -rf mkfs 至此一个mkfs的压缩文件mkfs.tar.gz就做好了。 通过这个过程再配备上面的expand脚本你应该知道制作的规则了吧。 下面用目录树了来表示这个架构:
bin sbin lib etc sshd以及ssh这两个程序依赖的库文件比较多,而且涉及的其它文件也比较多。然而它却是网关服务器所必不可少的。 要正确运行sshd要做以下几件事情 1. 安装sshd所依赖的库文件 2. 安装/lib/security目录 3. 安装/etc/pam.d文件夹 4. 安装/etc/ssh文件夹 5. 创建sshd账号 6. 创建/var/empty/sshd目录 下面是整个流程 #cd opt #mkdir sshd #cd sshd #mkdir bin etc lib #cp /usr/bin/ssh bin #cp /usr/sbin/sshd bin #ldd /usr/sbin/sshd libwrap.so.0 => /usr/lib/libwrap.so.0 (0×40026000) libpam.so.0 => /lib/libpam.so.0 (0x libdl.so.2 => /lib/libdl.so.2 (0×40037000) libresolv.so.2 => /lib/libresolv.so.2 (0x libutil.so.1 => /lib/libutil.so.1 (0x libz.so.1 => /usr/lib/libz.so.1 (0x libnsl.so.1 => /lib/libnsl.so.1 (0×4005e000) libcrypto.so.4 => /lib/libcrypto.so.4 (0×40073000) libkrb5.so.3 => /usr/kerberos/lib/libkrb5.so.3 (0×40164000) libk5crypto.so.3 => /usr/kerberos/lib/libk5crypto.so.3 (0x libcom_err.so.3 => /usr/kerberos/lib/libcom_err.so.3 (0×401d2000) libc.so.6 => /lib/tls/libc.so.6 (0×42000000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0×40000000) libgssapi_krb5.so.2 => /usr/kerberos/lib/libgssapi_krb5.so.2 (0×401d5000) (把这里面列出的库文件拷贝到lib目录下,注意拷贝的时候加上dpR参数,把原文件和链接文件全部拷贝过来) #cp -dpR /lib/security ./ (拷贝pam认证时需要的库文件) #cp -R /etc/ssh etc/ (拷贝ssh以及sshd的配置文件) #mkdir etc/pam.d #cp /etc/pam.d/system-auth etc/pam.d #cp /etc/pam.d/sshd etc/pam.d (把宿主机上的这两个文件拷贝过来,然后加以适当的修改,修改之后两个文件内容如下) #cat etc/pam.d/system-auth #%PAM-1.0 # This file is auto-generated. # User changes will be destroyed the next time authconfig is run. auth required /lib/security/pam_env.so auth sufficient /lib/security/pam_unix.so likeauth nullok auth required /lib/security/pam_deny.so
account required /lib/security/pam_unix.so
password required /lib/security/pam_cracklib.so retry=3 type= password sufficient /lib/security/pam_unix.so nullok use_authtok md5 shadow password required /lib/security/pam_deny.so
session required /lib/security/pam_limits.so session required /lib/security/pam_unix.so #cat etc/pam.d/sshd #%PAM-1.0 #auth required /lib/security/pam_securetty.so auth required /lib/security/pam_stack.so service=system-auth auth required /lib/security/pam_nologin.so account required /lib/security/pam_stack.so service=system-auth password required /lib/security/pam_stack.so service=system-auth session required /lib/security/pam_stack.so service=system-auth session optional /lib/security/pam_console.so (根据这两个文件所用到的/lib/security目录下的库文件,对上面拷贝过来的库文件做适当的裁减,下面是我裁减了的security下的库文件列表) #ls -l security -rwxr-xr-x 1 root root 9684 12月 19 15:21 pam_access.so -rwxr-xr-x 1 root root 47584 12月 19 15:21 pam_console.so -rwxr-xr-x 1 root root 12936 12月 19 15:21 pam_cracklib.so -rwxr-xr-x 1 root root 3404 12月 19 15:21 pam_deny.so -rwxr-xr-x 1 root root 11592 12月 19 15:21 pam_env.so -rwxr-xr-x 1 root root 8468 12月 19 15:21 pam_lastlog.so -rwxr-xr-x 1 root root 12324 12月 19 15:21 pam_limits.so -rwxr-xr-x 1 root root 4856 12月 19 15:21 pam_nologin.so -rwxr-xr-x 1 root root 3708 12月 19 15:21 pam_permit.so -rwxr-xr-x 1 root root 6544 12月 19 15:21 pam_securetty.so -rwxr-xr-x 1 root root 11132 12月 19 15:21 pam_stack.so lrwxrwxrwx 1 root root 11 12月 25 20:10 pam_unix_acct.so -> pam_unix.so lrwxrwxrwx 1 root root 11 12月 25 20:10 pam_unix_auth.so -> pam_unix.so lrwxrwxrwx 1 root root 11 12月 25 20:10 pam_unix_passwd.so -> pam_unix.so lrwxrwxrwx 1 root root 11 12月 25 20:10 pam_unix_session.so -> pam_unix.so -rwxr-xr-x 1 root root 48520 12月 19 15:21 pam_unix.so (注意就是动态链接库,它也有一定的依赖关系,因此最好对lib/security目录下的库文件依次检查其依赖关系,然后检查/mnt/rootfs/opt/sshd/lib目录下是否有其缺少的库文件,如果缺少的需要拷贝相应的动态链接库到/mnt/rootfs/opt/sshd/lib目录下.)
到这里,前4步已经做完了,第5步已经在前面的配置文件一节做完了。最后一步 #mkdir -p /mnt/rootfs/var/empty/sshd 下面我们把opt目录下的sshd文件夹打包压缩。 #cd /mnt/rootfs/opt/ #tar zcf sshd.tar.gz sshd #rm -rf sshd
到这里sshd工具就做完了,下面我们写个shell脚本,来控制sshd的调入与调出 脚本放在/sbin目录下,脚本文件名为ssh_sv,内容如下:
#!/bin/sh case "$1" in start) ssh_sv stop cd /tmp tar zxf /opt/sshd.tar.gz cd sshd mv etc/* /etc mv bin/ssh /usr/bin mv bin/sshd /sbin/sshd mv lib/* /usr/local/lib mv security /lib rm -rf /tmp/* expand ldconfig ldconfig depand ldconfig echo done;; stop)
rm -rf /lib/security rm -rf /etc/ssh rm -rf /etc/pam.d rm -rf /usr/bin/ssh rm -rf /sbin/sshd
for i in libcom_err.so libcom_err.so.3 libcom_err.so.3.0 \ libcrack.so libcrack.so.2 libcrack.so.2.7 \ libcrypto.so. libglib-1.2.so. libgssapi_krb5.so libgssapi_krb5.so.2 libgssapi_krb5.so.2.2 \ libk5crypto.so libk5crypto.so.3 libk5crypto.so.3.0 \ libkrb5.so libkrb5.so.3 libkrb5.so.3.1 libnsl- libnsl.so.1 libpam.so.0 libpam.so.0.75 libutil- libutil.so libutil.so.1 libwrap.so libwrap.so.0 \ libwrap.so. do rm -rf /usr/local/lib/$i done ;; *) echo "Usage: $0 {start|stop}" exit 1 esac
我们只要使用ssh_sv start 便可调入sshd与ssh,使用ssh_sv stop便可以调出sshd与ssh。 iptables.tar.gz 是我们网关服务器最重要的一个压缩包,我们的网关服务器的防火墙的配置,以及端口的映射等等都是通过iptables配置的。 #cd opt #mkdir iptables #cd iptables #mkdir lib sbin etc #cp -dpR /lib/iptables ./lib #cp /sbin/iptables ./sbin #touch etc/init.d/rc.firewall rc.firewall为网关服务器的防火墙以及端口映射配置脚本。 我贴一个例子到这里,关于iptables的使用方法可参考相关文档。 echo 1 > /proc/sys/net/ipv4/ip_forward /sbin/modprobe ip_conntrack /sbin/modprobe ip_conntrack_ftp /sbin/modprobe ip_nat_ftp /sbin/iptables -F INPUT /sbin/iptables -F OUTPUT /sbin/iptables -F FORWARD /sbin/iptables -t nat -F POSTROUTING /sbin/iptables -t nat -F PREROUTING /sbin/iptables -t nat -A POSTROUTING -o eth1 -s 192.168.0.0/24 -j MASQUERADE /sbin/iptables -t nat -A PREROUTING -i eth1 -p tcp –dport 80 -j DNAT –to-destination 192.168.0.1:80 我只解释前四句,其它的可以man iptables 第一句打开内核的ip转发功能 后三句加载必要的模块,当然如果这些模块是静态编译进内核的就不需要手动加载了。 #cd .. #tar zcf iptables.tar.gz iptables #rm -rf iptables 调入和调出iptables的脚本我就不说了,由前面的说明你应该能够轻松的写一个脚本出来。 5.9 根文件系统里面的最后一些东东 #cd var #ls empty lock log run #touch run/utmp (who命令读取的文件) #touch log/wtmp (last命令用到的配置文件) #touch log/lastlog (sshd开启登陆会话时读取的文件) #cd .. #mkdir -p usr/share/terminfo/l #cp /usr/share/terminfo/l/linux usr/share/terminfo/l (linux为grub成功启动所需要的终端) 6.创建ramdisk 第5章已经把根文件系统里面的内容做完了,现在我们建立ramdisk来存储根文件系统。 #du -ks rootfs 8356 我们的rootfs有 #cd /mnt #dd if=/dev/zero of=usblinux bs= (前面提到让ramdisk是虚拟的ram盘,它是在内存中虚拟出一块硬盘,因此我们需要对其进行格式化) #mkfs.ext2 -m 0 usblinux #mkdir usb #mount -o loop usblinux usb #cp -dpR /mnt/rootfs/* ./usb #umount usb #gzip -v9 usblinux 7 . 安装kernel ,ramdisk,grub到u盘 要想从U盘启动linux,可能需要把U盘格式化成启动盘,这里我们不关心这个,我们已经假定你的U盘可以以USB-HDD或者USB-ZIP方式启动。 插上准备好的U盘 #mount /dev/sda1 /mnt/usb (我这里的u盘设备是/dev/sda1,这个可以用fdisk -l命令查看) #cd /mnt/usb #mkdir boot/grub #cp /usr/src/linux-2.4/arch/i386/boot/bzImage boot/usblinux.kernel (把做好的内核拷贝到U盘上) #cp /mnt/usblinux.gz boot/usblinux.img (把做好的文件系统安装到U盘上) #cp /boot/grub/{stage1,stage2} boot/grub/ (在u盘上安装grub需要用到的文件) #vi boot/grub/grub.conf default=0 timeout=10 title UsbLinux root(hd0,0) kernel /boot/usblinux.kernel ramdisk_size=13000 ro root=/dev/ram0 initrd /boot/usblinux.img #cd ; umount /mnt/usb 最后一步就是安装grub到u盘上了。 #grub GNU GRUB version 0.95 (640K lower / 3072K upper memory) [ Minimal BASH-like line editing is supported. For the first word, TAB lists possible command completions. Anywhere else TAB lists the Possible completions of a device/filename.] grub>root (hd1,0) (注,u盘所在的设备号,不同的系统可能不一样,用fdisk -l先看看,不要搞错了,另外,注意设备的命名规则) grub>setup (hd1,0) grub>quit # (注:关于grub的安装及其使用可以参考grub手册 http://www./software/grub/manual/grub.html#initrd)
到此为止,应该说我们的目的基本完成了,不过启动的时候可能会出问题,请查看faq。 8.Faq 1. 为什么我的usblinux启动后,到uncompressing usblinux.kernel…………booting the kernel 之后就没有反应了呢? 答:这种情况我碰到的有两个可能的原因 1) 内核的cpu型号选择错误,譬如如果选择athon的cpu,那么在奔4的或者赛杨的机器上跑就会出现这种情况。我们的内核cpu型号选择i386就可以避免这种情况 2) 内核的Charater devices这一项里面没有把Virtual terminal 以及support console on virtual terminal两个选项编译进内核。 2. 内核为什么么启动到Free unused kernel memory *k,然后就没有反应了? 答:如果cpu选择386的话,而lib库还是使用的宿主机上的686的lib库,那么就会出现这种情况,在red hat 9上glibc库有386的版本和686的版本,我们需要从光盘上取出386的版本,然后用windows 下面的7zip工具把里面的相关库文件去出来,把我们的usblinux文件系统里面对应的库文件换掉即可。 3. 为什么busybox的init启动之后,在启动getty的终端输入root密码总是提示出错? 答:busybox的login/passwd认证有两种方法,一个是使用它内部的认证方法,这是无需nsswitch.conf的支持,另外一种就是使用linux系统的认证,这时需要nsswitch.conf文件。我们的密码一般放在/etc/shadow文件里面,但是我两种方法试了都不行,也让busybox支持了shadow,后来用askfirst动作得到一个shell进去之后,用passwd修改密码才发现它的密码是存在/etc/passwd里面的。 具体的修改密码后passwd变成下面这样的格式 root::$1$ccZny60u$o4MBUmzFjENUGWlVPKbTp.:0:0:root:/root:/bin/sh 而/etc/shadow文件里面的内容根本没有任何变化。 下面你知道怎么解决这个问题了吧。 4. 为什么我开启sshd服务后,远程登陆输入密码后没有反映了呢? ssh远程登陆到主机,需要主机的/dev/目录下有ptyp*设备以及ttyp*设备,当然如果内核支持pts文件系统,那么也可以使用pts/* 设备。 如果内核没有支持pts,或者pts文件系统没有正确安装,那么登陆会话就会使用ttyp*终端。但是如果没有ptyp*设备,就会出现上面的情况。
|
|