Linux内核字符设备开发小例子

其实很简单,简单理解了字符设备之后就是一个套模板编程呢个的问题了。这边简单的放一个例子

首先简要说明目录结构:我是在内核代码下直接新建了一个自己的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:

Linux内核字符设备开发小例子

编译完成后会生成可安装文件.ko文件

Linux内核字符设备开发小例子

2.安装module

安装命令为insmod,要注意使用管理员权限,命令前加sudo才行

安装完成后使用ls命令可以看到该模块已经成功安装好了

Linux内核字符设备开发小例子

3.创建设备节点

手动创建设备节点,创建成功后可以使用cat命令查看所有安装的设备,可以看到该设备节点已创建

Linux内核字符设备开发小例子

到这里字符设备就创建好了,接下来就是使用我们写的实例测试设备了

4.编译实例代码

使用gcc命令将上述的my_test_main.c实例遍历成可执行文件即可:

gcc my_test_main.c -o my_test_main

5.直接执行

Linux内核字符设备开发小例子

如上,可以看到我们的字符设备可以正常使用进行读写操作

上一篇:20210803:AXI-Stream协议源码分析初探


下一篇:《Linux内核设计与实现》笔记(四)