其实很简单,简单理解了字符设备之后就是一个套模板编程呢个的问题了。这边简单的放一个例子
首先简要说明目录结构:我是在内核代码下直接新建了一个自己的chrdev_test的文件夹,下面这些文件都放在该目录下。如果你不想这么做的话,起码要保证的是下面的.c和Makefile文件在一个目录下,不然没办法完成编译
<my_chrdev_test.c>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/timer.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/moduleparam.h>
#include "my_chrdev_test.h"
#define MAJOR_NUM 210 /*指定的主设备号*/
struct mycdev {
int len;
unsigned char buffer[50];
struct cdev cdev;
};
MODULE_LICENSE("GPL");
//设备号
static dev_t dev_num = {0};
//全局gcd
struct mycdev *gcd;
//设备类
struct class *cls;
//获得用户传输的数据,根据它来决定注册的设备的个数
static int ndevices = 1;
module_param(ndevices, int, 0644);
MODULE_PARM_DESC(ndevices, "The number of devices for register");
/*文件打开函数*/
static int dev_fifo_open(struct inode *inode, struct file *file)
{
struct mycdev *tdev = container_of(inode->i_cdev, struct mycdev, cdev);
file->private_data = tdev;
printk("test open success!");
return 0;
}
static long dev_fifo_unlock_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int ret = 0;
struct mycdev *mycd = file->private_data;
if (_IOC_TYPE(cmd) != DEV_FIFO_TYPE) {
pr_err("cmd %u, bad magic 0x%x/0x%x.\n", cmd, _IOC_TYPE(cmd), DEV_FIFO_TYPE);
return -ENOTTY;
}
if (_IOC_DIR(cmd)&_IOC_READ)
ret = !access_ok((void __user*)arg, sizeof(cmd)); // _IOC_SIZE(cmd));
else if( _IOC_DIR(cmd)&_IOC_WRITE )
ret = !access_ok((void __user*)arg, sizeof(cmd)); // _IOC_SIZE(cmd));
if (ret) {
pr_err("bad access %d.\n", ret);
return -EFAULT;
}
switch (cmd) {
case DEV_FIFO_CLEAN:
memset(mycd->buffer, 0, sizeof(mycd->buffer));
printk("test is set to zero\n");
printk("CMD: CLEAN\n");
break;
case DEV_FIFO_SETVALUE:
printk("CMD: SETVALUE\n");
mycd->len = arg;
break;
case DEV_FIFO_GETVALUE:
ret = put_user(mycd->len, (int *)arg);
printk("CMD: GETVALUE\n");
break;
default:
return -EFAULT;
}
return ret;
}
static ssize_t dev_fifo_read(struct file *file, char __user *ubuf, size_t size, loff_t * ppos)
{
int n;
int ret;
char *kbuf;
struct mycdev *mycd = file->private_data;
printk("write *ppos : %lld\n", *ppos);
//已经到达buffer尾部
if (*ppos == mycd->len)
return 0;
//请求大小 > buffer剩余的字节数:读取实际的字节数
if (size > mycd->len - *ppos)
n = mycd->len - *ppos;
else
n = size;
printk("n = %d\n", n);
//从上一次文件位置指针的位置开始读取数据
kbuf = mycd->buffer + *ppos;
//拷贝数据到用户空间
ret = copy_to_user(ubuf, kbuf, n);
if (ret != 0)
return -EFAULT;
//更新文件位置指针的值
*ppos += n;
printk("dev_fifo_read success!\n");
return n;
}
/*文件写操作*/
static ssize_t dev_fifo_write(struct file *file, const char __user *ubuf, size_t size, loff_t *ppos)
{
int n;
int ret;
char *kbuf;
struct mycdev *mycd = file->private_data;
printk("write *ppos: %lld\n", *ppos);
//已经到了buffer尾部
if (*ppos == sizeof(mycd->buffer))
return -1;
//请求大小 > buffer剩余的字节数,剩多少空间就写多少
if (size > sizeof(mycd->buffer) - *ppos)
n = sizeof(mycd->buffer) - *ppos;
else
n = size;
//从上一次文件位置指针的位置开始写入数据
kbuf = mycd->buffer + *ppos;
//将数据拷贝到内核空间
ret = copy_from_user(kbuf, ubuf, n);
if (ret != 0)
return -EFAULT;
//更新文件位置指针的值
*ppos += n;
//更新dev_fifo.len
mycd->len += n;
printk("dev_fifo_write success!\n");
return n;
}
/*文件操作结构体*/
static const struct file_operations fifo_operations = {
.owner = THIS_MODULE,
.open = dev_fifo_open,
.read = dev_fifo_read,
.write = dev_fifo_write,
.unlocked_ioctl = dev_fifo_unlock_ioctl,
};
/*设备驱动模块加载函数*/
static int __init dev_fifo_init(void)
{
int i = 0;
int n = 0;
int ret;
struct device *device;
gcd = kzalloc(ndevices * sizeof(struct mycdev), GFP_KERNEL);
if (!gcd) {
return -ENOMEM;
}
//设备号:主设备号(12bit)|次设备号(20bit)
dev_num = MKDEV(MAJOR_NUM, 0);
//静态注册设备号
ret = register_chrdev_region(dev_num, ndevices, "test");
if (ret < 0) {
printk("ERR: static register device number FAIL!\n");
//静态注册失败,进行动态注册
ret = alloc_chrdev_region(&dev_num, 0, ndevices, "test");
if (ret < 0) {
printk("ERR: dynamic register device number FAIL!\n");
goto err_register_chrdev_region;
}
}
//创建设备类
cls = class_create(THIS_MODULE, "test");
if (IS_ERR(cls)) {
ret = PTR_ERR(cls);
goto err_class_create;
}
printk("ndevices: %d\n", ndevices);
for (n = 0; n < ndevices; n++) {
//初始化字符设备
cdev_init(&gcd[n].cdev, &fifo_operations);
//添加设备到操作系统
ret = cdev_add(&gcd[n].cdev, dev_num + n, 1);
if (ret < 0) {
goto err_cdev_add;
}
//导出设备信息到用户空间(/sys/class/类名/设备名)
device = device_create(cls, NULL, dev_num + n, NULL, "test%d", n);
if (IS_ERR(device)) {
ret = PTR_ERR(device);
printk("Fail to device_create\n");
goto err_device_create;
}
}
printk("Register test to system, OK!\n");
return 0;
err_device_create:
//将已经导出的设备信息删除
for (i = 0; i < n; i++) {
device_destroy(cls, dev_num + i);
}
err_cdev_add:
//将已经添加的全部删除
for (i = 0; i < n; i++) {
cdev_del(&gcd[i].cdev);
}
err_class_create:
unregister_chrdev_region(dev_num, ndevices);
err_register_chrdev_region:
return ret;
}
module_init(dev_fifo_init);
/*设备驱动卸载函数*/
static void __exit dev_fifo_exit(void)
{
int i;
//删除sysfs文件系统中的设备
for (i = 0; i < ndevices; i++) {
device_destroy(cls, dev_num + i);
}
//删除系统中的设备类
class_destroy(cls);
//从系统中删除添加的字符设备
for (i = 0; i < ndevices; i++) {
cdev_del(&gcd[i].cdev);
}
//释放申请的设备号
unregister_chrdev_region(dev_num, ndevices);
printk("dev test exit");
return;
}
module_exit(dev_fifo_exit);
<my_chedev_test.h>
#ifndef _MY_CHRDEV_TEST_H
#define _MY_CHRDEV_TEST_H
#define DEV_FIFO_TYPE 'k'
#define DEV_FIFO_CLEAN _IO(DEV_FIFO_TYPE,0x10)
#define DEV_FIFO_GETVALUE _IOR(DEV_FIFO_TYPE,0x11,int)
#define DEV_FIFO_SETVALUE _IOW(DEV_FIFO_TYPE,0x12,int)
#endif
Makefile文件(这里要注意直接复制的话,可能贴进去后有缩进的几行会报错,把缩进的空格删掉后改称Tab缩进就好了)
ifeq ($(KERNELRELEASE),)
KERNEL_DIR ?=/lib/modules/$(shell uname -r)/build
PWD :=$(shell pwd)
modules:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules
.PHONY:modules clean
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) clean
else
obj-m := my_chrdev_test.o
endif
实例代码:
<my_test_main.c>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
int fd ;
int n;
char buf[1024] = "This is my first char device test process";
fd = open("/dev/test",O_RDWR);
if(fd < 0){
perror("Fail ot open");
return -1;
}
printf("open successful ,fd = %d\n",fd);
n = write(fd,buf,strlen(buf));
if(n < 0){
perror("Fail to write");
return -1;
}
printf("write %d bytes!\n",n);
n = read(fd,buf,strlen(buf));
if(n < 0){
perror("Fail to write");
return -1;
}
printf("read %d bytes!\n",n);
return 0;
}
代码就是上面这些
下面是操作实现的流程:
1.编译module
直接在目录下打开终端后make:
编译完成后会生成可安装文件.ko文件
2.安装module
安装命令为insmod,要注意使用管理员权限,命令前加sudo才行
安装完成后使用ls命令可以看到该模块已经成功安装好了
3.创建设备节点
手动创建设备节点,创建成功后可以使用cat命令查看所有安装的设备,可以看到该设备节点已创建
到这里字符设备就创建好了,接下来就是使用我们写的实例测试设备了
4.编译实例代码
使用gcc命令将上述的my_test_main.c实例遍历成可执行文件即可:
gcc my_test_main.c -o my_test_main
5.直接执行
如上,可以看到我们的字符设备可以正常使用进行读写操作