Linux字符设备驱动框架

推荐周立功先生的书籍《嵌入式Linux开发教程(下册)》,该书籍用于学习开发是不错的参考资料。

字符设备框架

char_dev_frame.c:

a 驱动程序
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>

static int major = 0;    /* 主设备号默认值 */
static int minor = 0;     /* 次设备号默认值 */

module_param(major, int, S_IRUGO);
module_param(minor, int ,S_IRUGO);

struct cdev *char_cdev;    /* c_dev数据结构 */
static dev_t dev_no;         /* 设备编号 */
static struct class *char_cdev_class;
static struct class_device  *char_cdev_class_device;

#define DEVICE_NAME    "char_cdev"
#define DEVICE_FILE_NAME "/dev/char_cdev"
static int char_cdev_open(struct inode *inode, struct file *file)
{
    try_module_get(THIS_MODULE);
    printk(KERN_INFO DEVICE_NAME"opened! \n");

    return 0;
}

static int char_cdev_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO DEVICE_NAME"closed! \n");
    module_put(THIS_MODULE);

    return 0;
}

static ssize_t char_cdev_read(struct file *file, char *buf, size_t count, loff_t *f_ops)
{
    printk(KERN_INFO DEVICE_NAME"read method! \n");

    return count;
}

static ssize_t char_cdev_write(struct file *file, const char *buf, size_t count, loff_t *f_ops)
{
    printk(KERN_INFO DEVICE_NAME"write method! \n");

    return count;
}

static int char_cdev_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
    printk(KERN_INFO DEVICE_NAME"ioctl method! \n");
    return 0;
}

struct file_operations char_cdev_fops = {
    .owner    = THIS_MODULE,
    .read     = char_cdev_read,
    .write    = char_cdev_write,
    .ioctl    = char_cdev_ioctl,
    .open     = char_cdev_open,
    .release  = char_cdev_release
};

static int __init char_cdev_init(void)
{
    int ret;

    /* 分配设备号 */
    if(major > 0) {    /* 静态分配 */
        dev_no = MKDEV(major, minor);
        ret = register_chrdev_region(dev_no, 1, DEVICE_NAME);
    } else {
        ret = alloc_chrdev_region(&dev_no, minor, 1, DEVICE_NAME);
        major = MAJOR(dev_no);
    }

    if(ret < 0) {
        printk(KERN_ERR"can't get major %d \n", major);
        return -1;
    }

    /* 注册设备 */
    char_cdev = cdev_alloc();       /* 分配cdev_设备结构 */
    if(char_cdev != NULL) {
        cdev_init(char_cdev, &char_cdev_fops);    /* 初始化char_cdev结构 */
        char_cdev->owner = THIS_MODULE;
        if(cdev_add(char_cdev, dev_no, 1) != 0) {
            printk(KERN_ERR"add cdev error!");
            goto error;
        }
    } else {
        printk(KERN_ERR"cdev_alloc_error! \n");
        return -1;
    }

    /* 注册设备节点 */
    char_cdev_class = class_create(THIS_MODULE, "char_cdev_class");
    if(IS_ERR(char_cdev_class)) {
        printk(KERN_INFO"create class error \n");

        return -1;
    }

    char_cdev_class_device = class_device_create(char_cdev_class, NULL, dev_no, NULL, "char_cdev");

    return 0;

error:
    unregister_chrdev_region(dev_no, 1);
    return ret;
}

static void __exit char_cdev_exit(void)
{
    cdev_del(char_cdev);
    unregister_chrdev_region(dev_no, 1);
    class_device_unregister(char_cdev_class_device);
    class_destroy(char_cdev_class);
}

module_init(char_cdev_init);
module_exit(char_cdev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("huchunrong");

添加设备节点时,因为kernel版本为2.6.22.6,应使用class_device_create,否则一直提示警告。后期的某个版本开始,class_device_createdevice_create替代。关于这两个函数的分析,网上分析很多。

b 驱动程序Makefile
# Makefile2.6
ifneq ($(KERNELRELEASE),)

# kbuild syntax. dependency relationshsip of files and target modules are listed here.
obj-m := char_dev_frame.o

else

PWD := $(shell pwd)
KVER := 2.6.22.6
KDIR := /home/eva/Developer/Linux/kernel/linux-2.6.22.6

all:
    $(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
    rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions

endif

上面的Makefile为Linux2.6内核下一段比较通用的Makefile。
aKERNELRELEASE是在内核源码的顶层Makefile中定义的一个变量,在第一次读取执行此Makefile时,KERNELRELEASE没有被定义,所以make将读取执行else之后的内容;
b 当make的目标为all时,-C $(KDIR)跳转到内核源码目录下读取Makefile;M=$(PWD) 然后返回到当前目录继续读入、执行当前的Makefile。

c 测试程序

char_dev_test.c:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <fcntl.h>

#define DEVICE_FILE_NAME "/dev/char_cdev"

int main(int argc, char *argv[])
{
    int fd, ret;
    char data;

    fd = open(DEVICE_FILE_NAME, O_RDWR);
    if(fd < 0) {
        perror("Open "DEVICE_FILE_NAME" Failed\n");
        exit(1);
    }

    ret = read(fd, &data, 1);
    if(!ret) {
        perror("Read "DEVICE_FILE_NAME" Failed\n");
        exit(1);
    }

    data = 1;
    ret = write(fd, &data, 1);
    if(!ret) {
        perror("Write "DEVICE_FILE_NAME" Failed\n");
        exit(1);
    }

    ret = ioctl(fd, 0, NULL);
    if(ret) {
        perror("Ioctl "DEVICE_FILE_NAME" Failed\n");
        exit(1);
    }

    close(fd);

    return 0;
}

编译

a 编译驱动

因为事先已经写好了Makefile,直接执行make命令即可

make
b 编译测试程序
arm-linux-gcc char_dev_test.c -o char_dev_test.o

加载和运行

拷贝可执行文件到nfs共享目录:cp char_dev_frame.ko char_dev_test.o ../../../bin/

在嵌入式设备上挂载nfs共享目录:mount -t nfs -o intr,nolock,rsize=1024,wsize=1024 192.168.1.12:/home/eva/Developer/Linux/bin /mnt

当文件比较大时,使用该指令成功率比较高

安装驱动程序:insmod char_dev_frame.ko
执行测试程序:./char_dev_test.o

如果驱动程序里没有进行创建设备文件,即没有执行class_device_create,也可以根据主设备号和次设备号,手动创建字符设备节点mknod /dev/char_cdev c 252 0

查看当前系统支持的设备cat /proc/devices
查看设备节点ls /dev

警告及错误

现象描述:
编译时在readwrite函数附近,提示warning: initialization from incompatible pointer type,一般原因为数据类型不匹配
解决措施:
定义read函数时,第三个参数类型为size_t,而不是ssize_t,改正后如下:static ssize_t char_cdev_read(struct file *file, char *buf, ssize_t count, loff_t *f_ops)write函数如此

现象描述:
编译时device_create函数提示警告warning: too many arguments for format,原因为jz2440的内核版本linux2.6.22.6,还不支持device_create函数

解决措施:
改用class_device_create函数,完整示例如下char_cdev_class_device = class_device_create(char_cdev_class, NULL, dev_no, NULL, "char_cdev");

现象描述:
insmod安装驱动后,无法在/dev/路径下找到设备节点,只能手动创建设备文件节点,但驱动程序里已经正确调用class_device_create函数

解决措施:
和烧写的文件系统有关,烧写fs_mini_mdev.yaffs2文件系统,而不是 fs_mini.yaffs2,具体原因未知

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值