Ubuntu编写字符设备驱动程序——内核树

本文详细介绍在Ubuntu系统中从源代码编译内核的过程,包括获取源码、配置、编译、安装新内核及生成initrd镜像文件的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 先查看自己OS使用的内核版本
uname -r
如果安装系统时,自动安装了源码。在 /usr/src 目录下有对应的使用的版本目录。例如下(我是自己下的)
shana@shana:/usr/src$ ls
linux-headers-2.6.22-14
linux-headers-2.6.22-14-generic
linux-source-2.6.22 /*这个就是解压后的源码目录 */
linux-source-2.6.22.tar.bz2 /* 这是我下的源码 包 */
shana@shana:/usr/src$

2. 如果没有源码。(一般ubuntu 都没有吧)
查看一下可一下载的源码包(切记不要使用超级用户使用此命令否则……会提示没有此命令)
shana@shana:/usr/src$ apt-cache search linux-source
linux-source - Linux kernel source with Ubuntu patches
xen-source-2.6.16 - Linux kernel source for version 2.6.17 with Ubuntu patches
linux-source-2.6.22 - Linux kernel source for version 2.6.22 with Ubuntu patches
shana@shana:/usr/src$
我选择了 linux-source-2.6.22 - Linux kernel source for version 2.6.22 with Ubuntu patches 这个~
然后 install 之
shana@shana:/usr/src$ sudo apt-get install linux-source-2.6.22

3. 下载完成后,在/usr/src下,文件名为:linux-source-2.6.22.tar.bz2,是一个压缩包,解压缩既可以得到整个内核的源代码:
注意 已经切换到超级用户模式
root@shana:/usr/src#tar jxvf linux-source-2.6.20.tar.bz2
解压后生成一个新的目录/usr/src/linux-source-2.6.22,所有的源代码都在该目录下。
4. cd到/usr/src/linux-2.6.29.4 目录下
# make mrproper (该命令可确保源代码目录下没有不正确的.o文件 .config文件等)
内核配置工具
(1)# make config (纯字符提示模式)
(2)# make menuconfig (配置内核各选项,居于ncurse 的菜单选择模式)
需要ncurse库的支持, ubuntu中默认没有安装 需要安装一下
# apt-get install libncurses5-dev
(3)# make xconfig (X窗口的图形界面,配置内核各选项)
xconfig基于X11, 使用qt库, 在Ubuntu中先安装qt库:
# apt-get install libqt3-headers libqt3-mt-dev
我是安装libncurses5-dev 之后用 menuconfig 来配置的, 都是用他默认配置,
如果希望个性一点,可以在General setup --->
() Local version - append to kernel release ()
这一项在内核版本后面加上个性字符串。配置好后保存配置文件就行了,会在 /usr/src/linux-2.6.29.4下生成.config 文件。当然名字都是自己定的。
5.编译内核
只需要运行 make就行了,这一句相当于了以前(2.4内核时期)的make bzImage 和makemodules两个命令。其中make bzImage 将在 /usr/src/linux-2.6.25.7-widebrihgt/arch/i386/boot 目录下生成一个名字为bzImage的内核镜像文件。make zImage 命令其实可以生成内核镜像的,bzImage 的区别就是它是压缩的。
make modules 则是生产大量的 *.ko内核文件,就是模块化的内核驱动等等吧,你不把它编译进去bzImage里面去的都会另外单独生成的一个ko文件了,以后需要的时候再由内核加载这个ko模块。
#make
执行之前可以先运行make clean清空生成的内核文件,或者编译中间有问题了,想重新编译,可以用这个先清空已经生成的内核文件。
注意事项:a、make一次花的编译时间应该在两个小时以上。千万别试图盯着它,最好有另一台电脑,或者是在虚拟机下编译,你就开个电影回来,应该差不多,如果不行,再看一部。
b、内核解压已经占去了1G多空间,编译还需要更大的空间,应该尽量保证剩余空间在2.5G以上,如果硬盘不够,可以mount一个U盘或者移动硬盘到linux-2.6.29.4。
6.安装内核

(1)执行 make install 命令
我运行了这一步后,提示需要在GRUB menu.lst 配置文件中把入口点指向 /boot/vmlinuz-2.6.29.4 文件。应该是已经把上面一步生成的bzImage复制到系统的 /boot 目录去了, ls -l看一下 /boot目录,可以已经把bzImage拷贝到/boot下变成vmlinuz-2.6.29.4,vmlinuz 文件链接到vmlinuz-2.6.29.4文件 System.map,文件链接到 System.map-2.6.25.7widebright文件,config 文件链接到 config-2.6.29.4文件。
(2)执行 make modules_install 命令,这个是安装模块的。这个会安装很多 ko文件,最后输出 DEPMOD2.6.29.4。在/lib/modules/目录下面生成了一个 2.6.29.4的目录,下面目录就是上面makemodules生成的那些ko内核模块文件。
(3)上面的编译过程好像没有生成initrd镜像文件,这点跟之前在RH和fedora中做的都不一样,上网查了下,只好手工生成initrd镜像文件,redhat系统下面用new-kernel-pkg命令应该就可以了,还有的用mkinitrd命令。 ubuntu系统应该是用 mkinittramfs命令来生成的。开始看了下系统中没有这个工具,用apt-get来获得:
#apt-get install kernel-package
#apt-get install initramfs-tools
运行一下 "mkinitramfs -o initrd.img-2.6.29.4 2.6.29.4" ,将在当前目录/boot下生成一个名字为initrd.img-2.6.29.4的initrd 镜像文件。注意:最后那个参数“2.6.29.4”是版本号,必须与/lib/modules/目录下相应的文件夹同名。
(4)设置grub的配置文件
cat /boot/grub/menu.lst看了一下,得知makeinstall命令没有自动把新编译的内核的配置写到grub的配置文

件里面,这一点跟以前编译的也不一样,之前是一句makeinstall就不用手工操作的。现在只好手动修改了,增加的内容如下:
title          Ubuntu 8.04.2, kernel 2.6.29.4
root           ()/ubuntu/disks
kernel         /boot/vmlinuz-2.6.29.4 root=UUID=9A3CB4D73CB4AF9Dloop=/ubun   tu/disks/root.disk ro quiet splash
initrd         /boot/initrd.img-2.6.29.4
注意其中的 kernel 和initrd 都指向自己的镜像文件。 root的UUID可能没个人都不一样的,可以参考GRUB的配置文件说明。
做完上面步骤之后,重启系统,等到系统提示按 ESC键的时候,可以选择和进入GRUB的菜单。可以看到自己的内核版本已经显示出来了。
选择进入之后,一切正常。
使用uname -a 命令查看,可以看到当前内核已经被更新了。
注: sudo aptitude purge ~ilinux-image-.*/(/!`uname -r`/) 可以彻底的把其他非正在只用的内核都删掉

=============================另附一篇initrd镜像相关文章==============================================
initrd 镜像文件的作用与制作

我们在安装新内核的时候,通常都会编辑lilo.conf 和grub.conf文件。里面有一段“initrd=/boot/initrd-***.img”(其中的***表示内核的版本号)的我们都很少会注意到,有时候还会发现这一行不要也可以正常开机。那这个initrd***.img究竟是用来干什么的呢?
其实initrd***.img是一个镜像文件,类似ramdisk,把一段程序打包到img里,然后在开机的时候在内存里开辟一段区域,一般是2m,释放到那里运行,都是一些初始化的程序,比如sisc_mod、ext3、
sd_mod等模块和insmod、nash等命令。不同内核,初始化的img可以相同,也可以不同,如果没有,可以在grub.conf里加上noinitrd,它就跳过initrd的检测和执行了,以前好像就没有,后来才有的。
它的作用是在没有mount/分区以前,系统要执行一些操作,比如挂载scsi驱动,它就把initrd释放到内存里,作一个虚拟的/,然后执行其根目录下的一个脚本“linuxrc”,运行insmod和nash命令挂装模块。
为什么有的时候我们在lilo或grub的配置文件中不加入该行都可以正常开机呢?这是因为我们一般的PC机都没有使用scsi硬盘等需要先加载其驱动的设备,所以就算没有initrd***.img也可以正常开机。但是
如果我们要在服务器上为其编译新内核那就一定不要忘记也为其新建一个initrd文件呀!
好,下面来看看如何查看initrd***.img里面的内容和如何为你的新内核创建一个新的initrd.img文件。
查看initrd.img:
initrd***.img虽然后缀是“img”,但其实它是一个gz格式的文件,我们可以先把它解压,然后载挂装到
目录下:
1、cp initrd.img initrd.gz
2、guzip initrd.gz
3、mount -o loop initrd /mnt/floppy
进入/mnt/floppy目录,我们就可以看到initrd***.img文件的庐山真面目了。
制作initrd文件
当我们编译了一个新的内核,也不要忘了为我们的scsi设备做一个新的initrd镜像:
语法:
mkinitrd 文件名 内核的目录名
示例:
mkinitrd initrd-2.4.19.img 2.4.19
initrd-2.4.19.img文件是自己任意取的,但最好对应自己的内核版本号。
2.4.19是在/lib/modules中的目录名,对应内核的版本。
当然,我们也可以在挂装了initrd***.img文件后直接添加模块到/lib目录中,然后修改linuxrc脚本让其开
机进行挂装。但必须具备一定的shell脚本的能力才行。

<think>我们即将编写一个简单的字符设备驱动模块,该模块的主要功能是: 1. 当用户向设备写入数据时,内核会将写入的内容打印出来 2. 可以通过模块参数`cap`控制输出是否转为大写(cap=1时转为大写) 3. 可以正常安装和卸载 步骤概览: 1. 准备开发环境(确保已安装内核头文件) 2. 编写内核模块代码(.c文件) 3. 编写Makefile 4. 编译模块 5. 加载模块(insmod)并创建设备节点(mknod) 6. 测试写入(使用echo或程序写入) 7. 卸载模块(rmmod) 详细步骤: 一、环境准备 确保当前系统已安装与当前内核版本对应的内核头文件。例如在Ubuntu上: sudo apt-get install linux-headers-$(uname -r) 二、编写代码(例如:my_char_dev.c) 代码结构: - 包含必要的头文件 - 定义设备结构体(存储设备状态) - 实现文件操作函数(open, release, write等) - 定义模块参数 - 模块初始化和退出函数 注意:由于我们只需要支持写操作,因此只需要实现write函数。 三、编写Makefile 指定内核源码路径,并编译模块。 四、编译模块 执行make命令 五、加载模块 - 使用insmod加载模块 - 使用`mknod`创建设备节点(需要主设备号) 六、测试 使用echo命令向设备写入字符串,通过dmesg查看内核输出。 七、卸载模块 使用rmmod卸载模块,并删除设备节点。 下面我们详细展开代码部分:</think>### 内核模块开发详细指南 以下是一个完整的字符设备驱动开发流程,包含详细步骤、原理说明和成功现象验证: --- ### 一、环境准备 #### 1. 安装编译依赖 ```bash sudo apt update sudo apt install build-essential linux-headers-$(uname -r) ``` **原理**:内核模块需要与当前内核版本匹配的头文件才能编译 **成功现象**:终端显示安装完成,无错误信息 --- ### 二、创建项目目录结构 ```bash mkdir char_dev_driver && cd char_dev_driver touch mychar.c Makefile ``` --- ### 三、编写驱动程序 (`mychar.c`) ```c #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/uaccess.h> #include <linux/slab.h> #define DEVICE_NAME "mychardev" #define CLASS_NAME "mychar" static int major_num; static struct class *char_class = NULL; static struct device *char_device = NULL; static struct cdev my_cdev; // 模块参数 static int cap = 0; module_param(cap, int, S_IRUGO); MODULE_PARM_DESC(cap, "Capitalize output (1=enable)"); // 设备打开 static int dev_open(struct inode *inodep, struct file *filep) { printk(KERN_INFO "Char device opened\n"); return 0; } // 设备写入 static ssize_t dev_write(struct file *filep, const char __user *buf, size_t len, loff_t *offset) { char *kernel_buf = kmalloc(len + 1, GFP_KERNEL); if (copy_from_user(kernel_buf, buf, len)) { kfree(kernel_buf); return -EFAULT; } kernel_buf[len] = '\0'; // 确保字符串终止 // 大写转换逻辑 if (cap) { char *p = kernel_buf; while (*p) { if (*p >= 'a' && *p <= 'z') *p -= 32; p++; } } printk(KERN_INFO "Received: %s\n", kernel_buf); kfree(kernel_buf); return len; } // 设备操作结构体 static const struct file_operations fops = { .owner = THIS_MODULE, .open = dev_open, .write = dev_write, }; // 模块初始化 static int __init char_dev_init(void) { // 分配设备号 if (alloc_chrdev_region(&major_num, 0, 1, DEVICE_NAME) < 0) { return -1; } // 创建设备类 char_class = class_create(THIS_MODULE, CLASS_NAME); if (IS_ERR(char_class)) { unregister_chrdev_region(major_num, 1); return PTR_ERR(char_class); } // 创建设备节点 char_device = device_create(char_class, NULL, major_num, NULL, DEVICE_NAME); if (IS_ERR(char_device)) { class_destroy(char_class); unregister_chrdev_region(major_num, 1); return PTR_ERR(char_device); } // 初始化字符设备 cdev_init(&my_cdev, &fops); if (cdev_add(&my_cdev, major_num, 1) < 0) { device_destroy(char_class, major_num); class_destroy(char_class); unregister_chrdev_region(major_num, 1); return -1; } printk(KERN_INFO "Char device loaded (cap=%d)\n", cap); return 0; } // 模块退出 static void __exit char_dev_exit(void) { device_destroy(char_class, major_num); class_destroy(char_class); cdev_del(&my_cdev); unregister_chrdev_region(major_num, 1); printk(KERN_INFO "Char device unloaded\n"); } module_init(char_dev_init); module_exit(char_dev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("Simple character device driver"); ``` --- ### 四、编写Makefile ```makefile obj-m += mychar.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean ``` --- ### 五、编译与安装模块 #### 1. 编译模块 ```bash make ``` **成功现象**:生成`mychar.ko`文件,无编译错误 #### 2. 加载模块(带参数) ```bash # 默认模式(不转换大写) sudo insmod mychar.ko # 带参数模式(启用大写转换) sudo insmod mychar.ko cap=1 ``` **成功现象**:`dmesg | tail`显示: ``` [ 123.456789] Char device loaded (cap=0) # 或cap=1 ``` #### 3. 验证设备节点 ```bash ls -l /dev/mychardev ``` **成功现象**:显示设备文件 `/dev/mychardev` --- ### 六、测试设备功能 #### 1. 写入测试数据 ```bash echo "Hello Kernel" > /dev/mychardev ``` #### 2. 查看内核输出 ```bash dmesg | tail -n 2 ``` **成功现象**: - `cap=0`时:`Received: Hello Kernel` - `cap=1`时:`Received: HELLO KERNEL` --- ### 七、卸载模块 ```bash sudo rmmod mychar ``` **成功现象**:`dmesg | tail`显示: ``` [ 234.567890] Char device unloaded ``` --- ### 关键原理说明 1. **字符设备注册**:通过`alloc_chrdev_region`动态分配设备号 2. **文件操作**:`file_operations`结构体定义设备的操作函数 3. **用户-内核空间交互**:`copy_from_user`安全复制用户空间数据 4. **模块参数**:`module_param`声明可通过insmod传递的参数 5. **设备节点**:`device_create`自动在/dev创建设备文件 6. **内存管理**:`kmalloc/kfree`动态分配内核内存
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值