本文記錄在 Ubuntu 3.8.0 核心上,參考《Linux 裝置驅動程式》(LDD3,針對 2.6 核心)編寫一個簡單的字元設備驅動 star。模組載入後透過 udev 自動建立 /dev/star0,讀取裝置節點可輸出指定數量的星號,數量由模組參數 howmany 動態指定。
star.h // 模組資訊巨集 star.c // 驅動主體 Makefile // 核心模組編譯腳本
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include "star.h"
#define STAR_MAJOR 0 // 動態分配主裝置號
#define STAR_DEVS 1
#define DEVICE_NAME "star0"
#define CLASS_NAME "star"
static int howmany = 5;
module_param(howmany, int, S_IRUGO); // 可透過 insmod howmany=10 指定
MODULE_AUTHOR("Tao Yang");
MODULE_DESCRIPTION("A CHAR DEVICE DRIVER SAMPLE USING UDEV");
MODULE_VERSION("0.1");
MODULE_LICENSE("GPL");
static struct class *star_class;
static struct device *star_sysdevice;
static int star_major;
static struct star_dev {
char *data;
struct cdev cdev;
} star_device;
// read: 輸出 howmany 個 '*'
static ssize_t star_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) {
char star_str[10000];
int i, len;
for (i = 0; i < howmany; i++) star_str[i] = '*';
star_str[howmany] = '\n';
len = howmany + 1;
if (count < len) return -EINVAL;
if (*ppos != 0) return 0;
if (copy_to_user(buf, star_str, len)) return -EINVAL;
*ppos = len;
return len;
}
static int star_open(struct inode *inode, struct file *filp) {
struct star_dev *dev = container_of(inode->i_cdev, struct star_dev, cdev);
filp->private_data = dev;
return 0;
}
static const struct file_operations star_fops = {
.owner = THIS_MODULE,
.read = star_read,
.open = star_open,
};
static void star_setup_cdev(struct star_dev *dev, int index) {
int err, devno = MKDEV(star_major, index);
cdev_init(&dev->cdev, &star_fops);
dev->cdev.owner = THIS_MODULE;
err = cdev_add(&dev->cdev, devno, 1);
if (err) printk(KERN_ALERT "Error %d adding star%d", err, index);
}
static int __init star_init(void) {
dev_t dev;
int ret;
printk(KERN_ALERT "hello tom!\n");
if (star_major) {
dev = MKDEV(star_major, 0);
ret = register_chrdev_region(dev, STAR_DEVS, DEVICE_NAME);
} else {
ret = alloc_chrdev_region(&dev, 0, STAR_DEVS, DEVICE_NAME);
star_major = MAJOR(dev);
printk(KERN_ALERT "dynamic! Major=%d\n", star_major);
}
if (ret < 0) return ret;
star_setup_cdev(&star_device, 0);
star_class = class_create(THIS_MODULE, CLASS_NAME);
star_sysdevice = device_create(star_class, NULL, MKDEV(star_major, 0), NULL, DEVICE_NAME);
return 0;
}
static void __exit star_exit(void) {
device_destroy(star_class, MKDEV(star_major, 0));
class_destroy(star_class);
cdev_del(&star_device.cdev);
printk(KERN_ALERT "goodbye...\n");
}
module_init(star_init);
module_exit(star_exit);
ifneq ($(KERNELRELEASE),)
obj-m := star.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
make
sudo insmod star.ko howmany=10
dmesg -c
# 輸出: dynamic! Major=249 等訊息
cat /dev/star0
**********
# 輸出 10 個星號
alloc_chrdev_region,避免衝突。class_create + device_create 配合 udev 自動建立 /dev/star0。module_param 實現運行時配置星號數量。copy_to_user 將核心資料安全複製到用戶空間。此範例為字元驅動最小原型,可擴展寫入、ioctl、非阻塞、輪詢等功能,奠定驅動開發基礎。