概要
Linux字符驱动入门,以Debian作为示例
准备工作
需要安装下面软件
$ sudo apt install -y gcc make linux-headers-$(uname -r)
软件名 | 作用 |
---|---|
gcc | 编译器 |
make | 自动化编译工具 |
linux-headers-$(uname -r) | linux头文件 |
字符驱动源码
下面源码保存到chrdev.c
#include <linux/module.h> /* Needed by all modules */
#include <linux/kernel.h> /* Needed for KERN_INFO */
#include <linux/init.h> /* Needed for the macros */
#include <linux/uaccess.h> /* copy_from_user & copy_to_user */
#include <linux/fs.h> /* file_operations */
#include <linux/cdev.h> /* cdev */
#define DEV_NAME "chardev"
#define DEV_CNT 2
#define BUFF_SIZE 128
//定义字符设备的设备号
static dev_t devno;
struct mychr
{
struct cdev chr_dev;
char vbuf[BUFF_SIZE];
char flag;
};
static struct mychr __mychr[DEV_CNT];
static int chr_dev_open(struct inode *inode, struct file *filp)
{
printk("open\n");
// container_of可以通过结构体的子元素找到结构体的基地址
// inode->i_cdev保持设备的cdev
filp->private_data = container_of(inode->i_cdev, struct mychr, chr_dev);
return 0;
}
static int chr_dev_release(struct inode *inode, struct file *filp)
{
printk("release\n");
return 0;
}
static ssize_t chr_dev_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
unsigned long p = *ppos;
int ret;
int tmp = count;
struct mychr *dev = filp->private_data;
char *vbuf = dev->vbuf;
if(p > BUFF_SIZE)
return 0;
ret = copy_from_user(vbuf, buf, tmp);
*ppos += tmp;
return tmp;
}
static ssize_t chr_dev_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
unsigned long p = *ppos;
int ret;
int tmp = count;
struct mychr *dev = filp->private_data;
char *vbuf = dev->vbuf;
if(p >= BUFF_SIZE)
return 0;
if (tmp > BUFF_SIZE - p)
tmp = BUFF_SIZE -p;
ret = copy_to_user(buf, vbuf+p, tmp);
*ppos += tmp;
return tmp;
}
static struct file_operations chr_dev_fops =
{
.owner = THIS_MODULE,
.open = chr_dev_open,
.release = chr_dev_release,
.read = chr_dev_read,
.write = chr_dev_write,
};
static int __init chrdev_init(void)
{
int ret = 0;
unsigned char i = 0;
printk("chrdev init\n");
// 第一步
// 采用动态分配得分方式,获取设备编号,次设备号为0
// 设备名称为 DEV_NAME,可通过命令 cat /proc/devices 查看
// DEV_CNT为申请设备编号数量
ret = alloc_chrdev_region(&devno, 0, DEV_CNT, DEV_NAME);
if(ret < 0)
{
printk("fail to alloc devno\n");
goto alloc_err;
}
// 第二步
// 关键字符设备结构体 cdev 与 文件操作结构体 file_operations
// 第三步
// 添加设备至cdev_map 散列表中
for(i = 0; i < DEV_CNT; i ++)
{
cdev_init(&__mychr[i].chr_dev, &chr_dev_fops);
ret = cdev_add(&__mychr[i].chr_dev, devno + i, 1);
if (ret < 0 )
{
printk("fail to add cdev\n");
goto add_err;
}
__mychr[i].flag = 1;
}
return 0;
add_err:
for(i = 0; i < DEV_CNT; i ++)
{
if(__mychr[i].flag == 1)
{
cdev_del(&__mychr[i].chr_dev);
}
__mychr[i].flag = 0;
}
// 添加设备失败时,需要注销设备号
unregister_chrdev_region(devno, DEV_CNT);
alloc_err:
return ret;
}
static void __exit chrdev_exit(void)
{
unsigned char i = 0;
printk("chrdev exit\n");
unregister_chrdev_region(devno, DEV_CNT);
for(i = 0; i < DEV_CNT; i ++)
{
cdev_del(&__mychr[i].chr_dev);
__mychr[i].flag = 0;
}
}
module_init(chrdev_init);
module_exit(chrdev_exit);
MODULE_LICENSE("GPL2");
Makefile
保存到Makefile
obj-m := chrdev.o
KDIR := /lib/modules/$(shell uname -r)/build
all:
make -C $(KDIR) M=$$PWD
clean:
rm chrdev.mod.c chrdev.o modules.order chrdev.mod.o Module.symvers
编译
执行如下命令
$ make
安装
$ sudo insmod chrdev.ko
$ cat /proc/devices
Character devices:
243 chardev
$ sudo mknod /dev/chardev1 c 243 0
$ sudo mknod /dev/chardev2 c 243 1
测试
$ echo "1111" > /dev/chardev1
$ echo "2222" > /dev/chardev2
$ cat /dev/chardev1
1111
$ cat /dev/chardev2
2222