alloc_chrdev_region:在未指定设备号的情况下使用
dev_t devid;
alloc_chrdev_region(&devid, 0, 1, "test");
major = MAJOR(devid); /* 获取主设备号 */
minor = MINOR(devid); /* 获取次设备号 */
register_chrdev_region:在给定设备号的情况下使用
int major;
int minor;
dev_t devid;
devid = MKDEV(major, minor);
register_chrdev_region(devid, 1, "test");
通 过 alloc_chrdev_region 或者 register_chrdev_region 申请的设备号,释放函数都是:unregister_chrdev_region。
class_register:把类注册进内核,类需要提前准备好。与class_unregister配对使用。
//用法一:先申请内存,再初始化
struct class *my_class;
my_class= kzalloc(sizeof(*my_class), GFP_KERNEL);
my_class->name = "my_class";
my_class->owner = THIS_MODULE;
class_register(my_class); // 会在/sys/class/下创建"my_class"目录
//用法二:定义结构体
struct class my_class= {
.name = "my_class",
.owner = THIS_MODULE,
};
class_register(&my_class); // 会在/sys/class/下创建"my_class"目录
class_create:创建类,并注册进内核,返回类结构体指针。与class_destroy配对使用。
struct class *my_class;
my_class= class_create(THIS_MODULE, "my_class"); // 会在/sys/class/下创建"my_class"目录
阅读class_create的源码会发现,class_create和class_register最后都调用了__class_register。两者都会在/sys/class/下创建设备类节点(是个文件夹)。
device_create:创建设备,需要传入class和设备号,返回device结构体指针。与device_destroy配对使用。
struct device *device;
device = device_create(class, NULL, devid, NULL, "device_name"); // 会在/dev/下创建"device_name"设备节点
内核源码中实际上device_create用的并不多,反而是device_register与device_add更常见,其实它们之间的调用关系是:device_create->device_registe->device_add。
通过上面的第一部分,可以完成一个字符设备的创建,并在/dev/下面生成对应的设备节点。但是此时还不能够对设备节点进行write/read/ioctl等操作,因为并没有为设备注册相应的驱动。cdev是Linux内核中的一个结构体,定义在
struct cdev my_cdev;
// 文件操作描述符
static struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = my_open,
.release = my_release,
.read = my_read,
.write = my_write,
.unlocked_ioctl = my_ioctl,
};
cdev_init(&my_cdev, &my_fops);
cdev_add(&my_cdev, devid, 1);
cdev_init完成对cdev结构体的初始化,cdev_add将cdev注册进内核,并与设备号关联起来。当应用层对设备节点进行操作时,会根据设备节点的设备号去内核中匹配对应的文件操作描述符,之后调用其中的回调函数。
卸载驱动时,使用cdev_del函数将字符设备驱动从内核中删除。
一个字符设备驱动除了包含必要设备号dev_t、所属类class、设备device、驱动cdev这些信息,此外还会有其它私有数据。为方便管理,将所有信息定义到一个结构体中。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define CLASS_NAME "my_class"
#define DEVICE_NAME "my_device"
#define BUFFER_SIZE 1024
#define MY_IOCTL_RESET _IO('M', 0)
struct chardev {
char name[32];
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
int minor;
};
static char buffer[BUFFER_SIZE];
static struct chardev my_dev;
static int my_open(struct inode *inode, struct file *file)
{
pr_info("%s\n", __func__);
return 0;
}
static int my_release(struct inode *inode, struct file *file)
{
pr_info("%s\n", __func__);
return 0;
}
static ssize_t my_read(struct file *file, char __user *user_buffer, size_t count, loff_t *offset)
{
ssize_t bytes_read = 0;
int remaining_bytes = 0;
remaining_bytes = BUFFER_SIZE - *offset;
if (remaining_bytes == 0) {
return 0;
}
bytes_read = min_t(size_t, count, remaining_bytes);
if (copy_to_user(user_buffer, buffer + *offset, bytes_read)) {
return -EFAULT;
}
*offset += bytes_read;
return bytes_read;
}
static ssize_t my_write(struct file *file, const char __user *user_buffer, size_t count, loff_t *offset)
{
ssize_t bytes_written = 0;
int remaining_bytes = 0;
remaining_bytes = BUFFER_SIZE - *offset;
if (remaining_bytes == 0) {
return -ENOSPC;
}
bytes_written = min_t(size_t, count, remaining_bytes);
if (copy_from_user(buffer + *offset, user_buffer, bytes_written)) {
return -EFAULT;
}
*offset += bytes_written;
return bytes_written;
}
static long my_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case MY_IOCTL_RESET:
memset(buffer, 0, BUFFER_SIZE);
pr_info("buffer reset\n");
break;
default:
return -EINVAL;
}
return 0;
}
static struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = my_open,
.release = my_release,
.read = my_read,
.write = my_write,
.unlocked_ioctl = my_ioctl,
};
static int __init chardev_init(void)
{
int ret;
// 1. 分配设备号
ret = alloc_chrdev_region(&my_dev.devid, 0, 1, "my_devid");
if (ret < 0) {
pr_err("Failed to allocate device number: %d\n", ret);
return ret;
}
my_dev.major = MAJOR(my_dev.devid);
my_dev.minor = MINOR(my_dev.devid);
pr_info("major = %d, minor = %d\n", my_dev.major, my_dev.minor);
// 2. 初始化cdev结构体、添加到内核
cdev_init(&my_dev.cdev, &my_fops);
ret = cdev_add(&my_dev.cdev, my_dev.devid, 1);
if (ret < 0) {
pr_err("Failed to add cdev: %d\n", ret);
unregister_chrdev_region(my_dev.devid, 1); // 记得注销设备号
return ret;
}
// 3. 创建类
my_dev.class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(my_dev.class)) {
ret = PTR_ERR(my_dev.class);
pr_err("Failed to create class: %d\n", ret);
return ret;
}
// 4. 创建设备
my_dev.device = device_create(my_dev.class, NULL, my_dev.devid, NULL, DEVICE_NAME);
if (IS_ERR(my_dev.device)) {
ret = PTR_ERR(my_dev.device);
pr_err("Failed to create device: %d\n", ret);
class_destroy(my_dev.class); // 记得注销类
return ret;
}
pr_info("%s done\n", __func__);
return 0;
}
static void __exit chardev_exit(void)
{
device_destroy(my_dev.class, my_dev.devid);
class_destroy(my_dev.class);
cdev_del(&my_dev.cdev);
unregister_chrdev_region(my_dev.devid, 1);
pr_info("%s done\n", __func__);
}
module_init(chardev_init);
module_exit(chardev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxx");
#include
#include
#include
#include
#include
#define DEVICE_PATH "/dev/my_device"
#define MY_IOCTL_RESET _IO('M', 0)
static int fd;
static char buffer[256];
int file_write(void)
{
ssize_t ret;
// 打开设备文件
fd = open(DEVICE_PATH, O_RDWR);
if (fd < 0) {
perror("Failed to open device");
return -1;
}
// 写入数据到设备文件
ret = write(fd, "Hello, device!", 14);
if (ret < 0) {
perror("Failed to write to device");
close(fd);
return -1;
}
printf("Write data to device: Hello, device!\n");
// 关闭设备文件
close(fd);
return 0;
}
int file_read(void)
{
ssize_t ret;
// 打开设备文件
fd = open(DEVICE_PATH, O_RDWR);
if (fd < 0) {
perror("Failed to open device");
return -1;
}
// 读取设备文件中的数据
ret = read(fd, buffer, sizeof(buffer));
if (ret < 0) {
perror("Failed to read from device");
close(fd);
return -1;
}
printf("Read data from device: %s\n", buffer);
// 关闭设备文件
close(fd);
return 0;
}
int file_ioctl(void)
{
ssize_t ret;
// 打开设备文件
fd = open(DEVICE_PATH, O_RDWR);
if (fd < 0) {
perror("Failed to open device");
return -1;
}
// 重置设备文件中的数据
ret = ioctl(fd, MY_IOCTL_RESET);
if (ret < 0) {
perror("Failed to ioctl");
close(fd);
return -1;
}
printf("Device buffer reset\n");
// 关闭设备文件
close(fd);
return 0;
}
int main(void)
{
file_write();
file_read();
file_ioctl();
return 0;
}