本文主要用于记录操作,包括linux内核的编译过程,入门驱动的程序编写,相关命令或术语简介,参考价值有限...
0. 最好不要在虚拟机编译,内存容易出问题;注意权限的使用;遇到的若干Error不再赘述,网上有成堆的解决方案。
1. 查看系统和内核版本,下载内核并解压。这里我下载了相近的3.14.63版本。
1 2 3 |
lsb_release -a uname -r tar xzvf linux-3.14.63 |
2. 配置内核,得到.config文件。
1 2 |
cd Workspace/linux-3.14.63 // 进入内核根目录 make menuconfig // 启动配置 |
为方便,将/boot/目录下的配置文件,这里我的是config-3.13.0-79-generic,复制到内核根目录下。然后在编译界面选择<Load>,填写刚刚的文件名,<OK>返回上层,<Save>,将文件名命名为.config,<OK>返回上层,<Exit>。回到内核根目录使用ls -a 查看配置文件,如果存在.config文件则说明配置成功。
3. 编译内核,得到内核映像zImage和内核模块映像initrd-$version。
1 2 3 |
make bzImage // 编译完成后会在~/Workspace/linux-3.14.63/arch/X86/boot/下找到一个bzImage的内核映像文件,15mins make modules // 编译内核模块,需要用到才加载,*.ko,1h+ make modules_install // 执行完这个命令之后到/lib/modules/目录下可以看到相应的目录 |
前面得到了bzImage内核镜像,bzImage和vmlinuz是完全一样的东西,只是习惯将bzImage放到/boot目录下时重名名为vmlinuz;接下来需要制作initrd文件,initrd全称是boot loader initialized RAM disk,就是由boot loader初始化的内存盘。在linux内核启动前, boot loader会将存储介质中的 initrd文件加载到内存,内核启动时会在访问真正的根文件系统前先访问该内存中的initrd文件系统。在boot loader 配置了initrd的情况下,内核启动被分成了两个阶段,第一阶段先执行initrd文件系统,完成加载驱动模块等任务,第二阶段才会执行真正的根文件系统中的/sbin/init 进程。
1 2 3 |
sudo cp arch/x86/boot/bzImage /boot/vmlinuz-3.14.63 // copy sudo cp .config /boot/config-3.14.63 // copy sudo mkinitramfs -o initrd-3.14.63 3.14.63 |
4. 安装内核,经过前面的编译阶段,我们得到了bzImage 和initrd-3.14.63 ,将这两个文件copy 到/boot/目录下,修改启动项/etc/grub.conf里面的加载内核文件和内核模块的部分,并更新。
1 2 |
sudo gedit /boot/grub // 编辑,添加新内核 sudo update-grub2 |
5. 清除文件,安装完成以后还得把内核代码里留下的痕迹清理。
1 |
make distclean |
6. hello world驱动编写。编写my_module.c和Makefile文件。注:做这部分的时候到实验室换了台机器...其实没啥区别。
1 2 3 4 5 |
#define module_init(x) __initcall(x); #define __initcall(fn) device_initcall(fn) #define device_initcall(fn) __define_initcall("6",fn,6) #define __define_initcall(level,fn,id) \ static initcall_t __initcall_##fn##id __used__attribute__((__section__(".initcall" level ".init"))) = fn |
看上述代码,假设driver对应模块的初始化函数为int test_init(void),那么module_init(test_init)实际上等于: static initcall_t __initcall_test_init_6 __used __attribute__((__section__(".initcall6.init"))) = test_init; 即,声明了一个类型为initcall_t (typedef int (*initcall_t)(void))函数指针类型的变量__initcall_test_init_6并将test_init赋值给它。这里的函数指针变量声明比较特殊的地方在于,将这个变量放在了一个叫做".initcall6.init"的地方,结合vmlinux.lds:
1 2 3 4 5 6 |
.initcall.init : AT(ADDR(.initcall.init) - (0xc0000000 -0x00000000)) { __initcall_start = .; *(.initcallearly.init) __early_initcall_end = .; *(.initcall0.init) *(.initcall0s.init) *(.initcall1.init) *(.initcall1s.init) *(.initcall2.init) *(.initcall2s.init) *(.initcall3.init) *(.initcall3s.init) *(.initcall4.init) *(.initcall4s.init) *(.initcall5.init) *(.initcall5s.init) *(.initcallrootfs.init) <strong>*(.initcall6.init)</strong> *(.initcall6s.init) *(.initcall7.init) *(.initcall7s.init) __initcall_end = .; } |
以及do_initcalls:
1 2 3 4 5 6 7 |
static void __init do_initcalls(void) { // 完成__initcall段中的所有函数指针所指向的函数的调用 initcall_t *call; for (call = __initcall_start; call < __initcall_end; call++) do_one_initcall(*call); /* Make sure there is no pending stuff from the initcall sequence */ flush_scheduled_work(); } |
此时可见module_init中初始化函数的调用过程,在系统启动过程中:start_kernel()->rest_init()->kernel_init()->do_basic_setup()->do_initcalls()。module_exit分析同理。
7. 编译驱动,并加、卸载,tty1查看运行输出。由于printk的优先级问题,选择了tty打印。
1 2 3 |
sudo make sudo insmod ./my_module.ko sudo rmmod my_module |
8. 在/sys/目录下查看编译的驱动文件。
Comments are closed.