利用本文中介绍的这些技巧,我们可以在一个双引导的机器上自动从一个操作系统切换到另外一个操作系统,无需任何人工干预。您可以使用本文提供的脚本在自己的机器上复制这种同时运行 Linux? 和 Windows? 的设置。
既然我们可以非常简单地直接手工实现这种操作系统的切换,为什么还希望自动化此操作呢?简单的答案是自动化过程可以让使用多个操作系统变得容易得多。例如您要在多个操作系统平台上测试软件,那么这种功能就会特别有用。
本文在介绍这些技巧时假设您已经安装好了操作系统,并使用 GRUB 配置了多重引导。GRUB 是 GRand Unified Bootloader 的简称,它在机器启动时加载,然后机器的控制权被转交给 OS 内核软件。有关部署 GRUB 的帮助信息,请参阅本文后面的 参考资料 一节。
本文介绍的技巧可以适用于:
Microsoft? Windows XP Professional Microsoft Windows Server 2003 Debian Linux 3.1(Sarge)
Red Hat Enterprise Server(RHES) 3 GRUB 0.97;虽然使用这个版本的 GRUB 成功了,但不保证使用其他 bootloader 也会获得成功步骤 1. 设置磁盘分区
在开始配置系统之前,确保有一个最新的系统备份,并且手头有一张援助用的 CD.如果在执行这些步骤时出现了问题,那么机器可能无法启动。如果发现自己面临的风险是无法接受的,那么最好不要继续执行以下步骤。
创建 Bootcontrol 分区
在一块硬盘上创建一个小分区。在这个分区上必须创建这样一个文件系统:要切换的所有操作系统都能够加载这个文件系统,并可以对这个文件执行写入操作。我们选择使用 FAT32.尽管整个 Bootcontrol 系统只需要不到 1MB 的磁盘空间,但是 FAT32 默认最小限制为 256MB,因此这会浪费一些空间。
如果没有足够的未分配空间,可以通过缩小或删除现有分区来创建这部分空间。 在 Linux 中可以使用 GNU parted 命令来实现这项功能。如果在执行 parted 操作之后,现有分区的次序发生了变化,那么可能还需要更新 /etc/fstab 文件。有关的更多信息,请参阅 parted 的文档。
当使用 parted 在测试机器上创建所需的分区时,会看到如清单 1 中所示的结果:
清单 1. 创建必需的分区
repton:~# cat /etc/fstab
# /etc/fstab: static file system information.
# <file system> <mount point> <type> <options> <dump> <pass>
proc /proc proc defaults 0 0
/dev/hda2 / ext3 defaults 0 1
/dev/hda6 /home ext3 defaults 0 2
/dev/hda7 /opt ext3 defaults 0 2
/dev/hda5 none swap sw 0 0
/dev/hdc /media/cdrom0 iso9660 ro,user,noauto 0 0
/dev/fd0 /media/floppy0 auto rw,user,noauto 0 0
repton:~# umount /home
repton:~# parted
Using /dev/hda
(parted) print
Disk geometry for /dev/hda: 0.000-57231.562 megabytes
Disk label type: msdos
Minor Start End Type Filesystem Flags
1 0.031 18412.734 primary ntfs boot
2 18418.271 25085.874 primary ext3
3 25085.874 57231.562 extended
5 25085.905 26458.615 logical linux-swap
6 26458.646 49999.174 logical ext3
7 49999.206 57231.562 logical ext3
(parted) resize 6 26458 49739
(parted) mkpartfs logical fat32 49739 49999
(parted) print
Disk geometry for /dev/hda: 0.000-57231.562 megabytes
Disk label type: msdos
Minor Start End Type Filesystem Flags
1 0.031 18412.734 primary ntfs boot
2 18418.271 25085.874 primary ext3
3 25085.874 57231.562 extended
5 25085.905 26458.615 logical linux-swap
6 26458.646 49740.314 logical ext3
8 49740.346 49999.174 logical fat32
7 49999.206 57231.562 logical ext3
(parted) q
repton:~# mount /home
挂载 Linux 分区
在为控制分区创建好空间之后,需要将其挂载到 Linux 中,这样就可以在 Linux 中看到这个分区。在本例中,我们将以下内容添加到了 /etc/fstab 中:
# <file system> <mount point> <type> <options> <dump> <pass> /dev/hda8 /boot/control vfat umask=022,dmask=022,fmask=022 0 2然后使用下面的命令来创建挂载点并挂载这个文件系统:
mkdir /boot/control mount /boot/control
还需要更新 GRUB 在分区结果上维护的信息。在这个测试系统上,我们使用下面的命令:grub-install /dev/hda.
挂载 Windows 分区
通过(手工)重新启动到 Windows,验证上面的分区编辑步骤不会破坏 Windows 的引导过程。 然后为刚才创建的 Bootcontrol 卷分配一个驱动器字符。在现代版的 Windows 上,可以使用 Computer Management MMC snap-in(右键点击 My Computer,然后选择 Manage)。在以前的版本上,可以选择 Start > Administrative Tools.
清单 4. 编译 LKM
[root@plato]# make -C /usr/src/linux-`uname -r` SUBDIRS=$PWD modules make: Entering directory `/usr/src/linux-2.6.11' CC [M] /root/projects/misc/module2.6/simple/simple-lkm.o Building modules, stage 2. MODPOST CC /root/projects/misc/module2.6/simple/simple-lkm.mod.o LD [M] /root/projects/misc/module2.6/simple/simple-lkm.ko make: Leaving directory `/usr/src/linux-2.6.11' [root@plato]#
结果会生成一个 simple-lkm.ko 文件。这个新的命名约定可以帮助将这些内核对象(LKM)与标准对象区分开来。现在可以加载或卸载这个模块了,然后可以查看它的输出。要加载这个模块,请使用 insmod 命令;反之,要卸载这个模块,请使用 rmmod 命令。lsmod 可以显示当前加载的 LKM(参见清单 5)。
清单 5. 插入、检查和删除 LKM
[root@plato]# insmod simple-lkm.ko [root@plato]# lsmod Module Size Used by simple_lkm 1536 0 autofs4 26244 0 video 13956 0 button 5264 0 battery 7684 0 ac 3716 0 yenta_socket 18952 3 rsrc_nonstatic 9472 1 yenta_socket uhci_hcd 32144 0 i2c_piix4 7824 0 dm_mod 56468 3 [root@plato]# rmmod simple-lkm [root@plato]#
注意,内核的输出进到了内核回环缓冲区中,而不是打印到 stdout 上,这是因为 stdout 是进程特有的环境。要查看内核回环缓冲区中的消息,可以使用 dmesg 工具(或者通过 /proc 本身使用 cat /proc/kmsg 命令)。清单 6 给出了 dmesg 显示的最后几条消息。
清单 6. 查看来自 LKM 的内核输出
[root@plato]# dmesg tail -5 cs: IO port probe 0xa00-0xaff: clean. eth0: Link is down eth0: Link is up, running at 100Mbit half-duplex my_module_init called. Module is now loaded. my_module_cleanup called. Module is now unloaded. [root@plato]#
可以在内核输出中看到这个模块的消息。现在让我们暂时离开这个简单的例子,来看几个可以用来开发有用 LKM 的内核 API。
集成到 /proc 文件系统中
内核程序员可以使用的标准 API,LKM 程序员也可以使用。LKM 甚至可以导出内核使用的新变量和函数。有关 API 的完整介绍已经超出了本文的范围,因此我们在这里只是简单地介绍后面在展示一个更有用的 LKM 时所使用的几个元素。
创建并删除 /proc 项
要在 /proc 文件系统中创建一个虚拟文件,请使用 create_proc_entry 函数。这个函数可以接收一个文件名、一组权限和这个文件在 /proc 文件系统中出现的位置。create_proc_entry 的返回值是一个 proc_dir_entry 指针(或者为 NULL,说明在 create 时发生了错误)。然后就可以使用这个返回的指针来配置这个虚拟文件的其他参数,例如在对该文件执行读操作时应该调用的函数。create_proc_entry 的原型和 proc_dir_entry 结构中的一部分如清单 7 所示。
清单 7. 用来管理 /proc 文件系统项的元素
struct proc_dir_entry *create_proc_entry( const char *name, mode_t mode,
struct proc_dir_entry *parent );
struct proc_dir_entry {
const char *name; // virtual file name
mode_t mode; // mode permissions
uid_t uid; // File's user id
gid_t gid; // File's group id
struct inode_operations *proc_iops; // Inode operations functions
struct file_operations *proc_fops; // File operations functions
struct proc_dir_entry *parent; // Parent directory
...
read_proc_t *read_proc; // /proc read function
write_proc_t *write_proc; // /proc write function
void *data; // Pointer to private data
atomic_t count; // use count
...
};
void remove_proc_entry( const char *name, struct proc_dir_entry *parent );
稍后我们就可以看到如何使用 read_proc 和 write_proc 命令来插入对这个虚拟文件进行读写的函数。
要从 /proc 中删除一个文件,可以使用 remove_proc_entry 函数。要使用这个函数,我们需要提供文件名字符串,以及这个文件在 /proc 文件系统中的位置(parent)。这个函数原型如清单 7 所示。
parent 参数可以为 NULL(表示 /proc 根目录),也可以是很多其他值,这取决于我们希望将这个文件放到什么地方。表 1 列出了可以使用的其他一些父 proc_dir_entry,以及它们在这个文件系统中的位置。
表 1. proc_dir_entry 快捷变量
回调函数
我们可以使用 write_proc 函数向 /proc 中写入一项。这个函数的原型如下:
int mod_write( struct file *filp, const char __user *buff,
unsigned long len, void *data );
filp 参数实际上是一个打开文件结构(我们可以忽略这个参数)。buff 参数是传递给您的字符串数据。缓冲区地址实际上是一个用户空间的缓冲区,因此我们不能直接读取它。len 参数定义了在 buff 中有多少数据要被写入。data 参数是一个指向私有数据的指针(参见 清单 7)。在这个模块中,我们声明了一个这种类型的函数来处理到达的数据。
Linux 提供了一组 API 来在用户空间和内核空间之间移动数据。对于 write_proc 的情况来说,我们使用了 copy_from_user 函数来维护用户空间的数据。
读回调函数
我们可以使用 read_proc 函数从一个 /proc 项中读取数据(从内核空间到用户空间)。这个函数的原型如下:
int mod_read( char *page, char **start, off_t off,
int count, int *eof, void *data );


