1. 前言
本文基于Ubuntu 22.04.2 LTS(Linux version 5.19.0-41-generic),主要是实现一个简单的字符设备驱动,以秒为单位记录时间。并且,使用应用程序验证获取当前时间。
2. 编译并加载内核驱动
//timer_cdev.c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/init.h>
#include <linux/cdev.h>
#define SECOND_MAJOR 155
static int second_major = SECOND_MAJOR;
module_param(second_major, int, S_IRUGO);
struct second_dev {
struct cdev cdev;
atomic_t counter;
struct timer_list s_timer;
};
static struct second_dev *second_devp;
static void second_timer_handler(struct timer_list* arg)
{
mod_timer(&second_devp->s_timer, jiffies + HZ);
atomic_inc(&second_devp->counter);
printk(KERN_ERR "current jiffies is %ld\n", jiffies);
}
static int second_open(struct inode* inode, struct file *filp)
{
#if 0
init_timer(&second_devp->s_timer);
second_devp->s_timer.function = &second_timer_handler;
second_devp->s_timer.expires = jiffies + HZ;
add_timer(&second_devp->s_timer);
#else
timer_setup(&second_devp->s_timer, second_timer_handler, 0);
add_timer(&second_devp->s_timer);
#endif
atomic_set(&second_devp->counter, 0); /* 初始化秒计数为0 */
return 0;
}
static int second_release(struct inode *inode, struct file *filp)
{
del_timer(&second_devp->s_timer);
return 0;
}
static ssize_t second_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
int counter;
counter = atomic_read(&second_devp->counter);
if(put_user(counter, (int *)buf))
return -EFAULT;
else
return sizeof(unsigned int);
}
static const struct file_operations second_fops = {
.owner = THIS_MODULE,
.open = second_open,
.release = second_release,
.read = second_read,
};
static void second_setup_cdev(struct second_dev *dev, int index)
{
int err, devno = MKDEV(second_major, index);
cdev_init(&dev->cdev, &second_fops);
dev->cdev.owner = THIS_MODULE;
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk(KERN_ERR "Failed to add second device\n");
class_create(THIS_MODULE, "second"); // 在/dev目录下创建second设备节点
}
static int __init second_init(void)
{
printk(KERN_ERR "enter second_init, second_major:%d\n", second_major);
int ret;
dev_t devno = MKDEV(second_major, 0);
if(second_major) {
ret = register_chrdev_region(devno, 1, "second");
} else {
ret = alloc_chrdev_region(&devno, 0, 1, "second");
}
if(ret < 0) {
printk(KERN_ERR "Failed to register chrdev\n");
return ret;
}
second_devp = kzalloc(sizeof(*second_devp), GFP_KERNEL);
if(!second_devp) {
ret = -ENOMEM;
printk(KERN_ERR "kzalloc failed\n");
goto fail_malloc;
}
second_setup_cdev(second_devp, 0);
printk(KERN_ERR "exit second_init\n");
return 0;
fail_malloc:
unregister_chrdev_region(devno, 1);
return ret;
}
static void __exit second_exit(void)
{
cdev_del(&second_devp->cdev);
kfree(second_devp);
unregister_chrdev_region(MKDEV(second_major, 0), 1);
}
module_init(second_init);
module_exit(second_exit);
MODULE_AUTHOR("littleSnail");
MODULE_LICENSE("GPL v2");
Makefile:
KVER:=$(shell uname -r)
#注意Ubuntu内核编译的gcc和指定的gcc要一致
CC:=x86_64-linux-gnu-gcc
obj-m := timer_cdev.o
build: kernel_modules
kernel_modules:
make CC=$(CC) -C /lib/modules/$(KVER)/build M=$(CURDIR) modules
clean:
make CC=$(CC) -C /lib/modules/$(KVER)/build M=$(CURDIR) clean
执行如下命令:
make
insmod timer_cdev.ko second_major=179
执行完成后会看到字符设备**/dev/second**。
3. 编译并执行应用程序
//main.c
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int main()
{
int fd = -1;
int ret = 0;
unsigned long current = 0;
unsigned long pre = 0;
fd = open("/dev/second", O_RDONLY);
if(fd < 0)
{
printf("open /dev/second error!\n");
return -1;
}
while(1)
{
if(ret = read(fd, ¤t, sizeof(long)) <= 0) {
printf("read error!\n");
break;
}
if(current != pre) {
printf("current:%ld\n", current);
pre = current;
}
}
close(fd);
return ret;
}
执行命令:
x86_64-linux-gnu-gcc -g main.c -o main
执行结果如下:
root@virtual-machine:/home/timer_cdev# ./main
current:1
current:2
current:3
current:4
current:5
current:6
current:7
current:8
current:9
4. 参考文献
《Linux设备驱动开发详解:基于最新的Linux 4.0内核》宋宝华著