简单字符设备程序

Linux下的驱动有字符设备驱动,块设备驱动和网络设备驱动。无论哪一种设备驱动,为了减少内核的体积和易于内核的管理,驱动多以模块的形式出现。对于内核模块的编程,前面一节已经提到过,主要包括内核的加载函数和卸载函数,模块的作者和GPL协议。

对于简单的字符设备驱动而言,其实编写流程很简单,只要记住这个流程,就可以快速的写一个简单的字符设备驱动玩了。其流程主要有:

  1. 内核模块的加载和卸载
  2. 添加并实现file_operations设备驱动文件操作结构体
  3. 初始化并添加cdev结构体
  4. 实现operation中的函数体

内核模块的加载与卸载函数就相当简单了

//添加头文件
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

//模块加载
int __init mymodule_init(void)
{
    return 0;
}
//模块卸载
void __exit mymodule_exit(void)
{
    
}

module_init(mymodule_init);
module_exit(mymodule_exit);
MODULE_AUTHOR("HaoRan");
MODULE_LICENSE(“GPL”);

 添加并实现file_operations设备驱动文件操作结构体

file_operation 结构体的成员函数是字符设备驱动程序设计的主要内容。这里只添加其中的一部分函数。

struct file_operations
{
    struct module *owner;       // 拥有该结构的模块的指针,一般为 THIS_MODULES                  
    loff_t(*llseek)(struct file *, loff_t, int);       // 用来修改文件当前的读写位置       
    ssize_t(*read)(struct file *, char _ _user *, size_t, loff_t*);   // 从设备中同步读取数据     
    ssize_t(*aio_read)(struct kiocb *, char _ _user *, size_t, loff_t);      // 初始化一个异步的读取操作 
    ssize_t(*write)(struct file *, const char _ _user *, size_t, loff_t*);       // 向设备发送数据 
    ssize_t(*aio_write)(struct kiocb *, const char _ _user *, size_t, loff_t);       // 初始化一个异步的写入操作 
    int(*readdir)(struct file *, void *, filldir_t);       // 仅用于读取目录,对于设备文件,该字段为 NULL 
    unsigned int(*poll)(struct file *, struct poll_table_struct*);      // 轮询函数,判断目前是否可以进行非阻塞的读取或写入 
    int(*ioctl)(struct inode *, struct file *, unsigned int, unsigned long);       // 执行设备 I/O 控制命令 
    long(*unlocked_ioctl)(struct file *, unsigned int, unsigned long);     // 不使用 BLK 文件系统,将使用此种函数指针代替 ioctl 
    long(*compat_ioctl)(struct file *, unsigned int, unsigned long);       // 在 64 位系统上,32 位的 ioctl 调用将使用此函数指针代替
    int(*mmap)(struct file *, struct vm_area_struct*);       // 用于请求将设备内存映射到进程地址空间 
    int(*open)(struct inode *, struct file*);   // 打开 
    int(*flush)(struct file*); 30   int(*release)(struct inode *, struct file*);       // 关闭 
    int(*synch)(struct file *, struct dentry *, int datasync);       // 刷新待处理的数据 
    int(*aio_fsync)(struct kiocb *, int datasync);    // 异步 fsync 
    int(*fasync)(int, struct file *, int);     // 通知设备 FASYNC 标志发生变化 
    int(*lock)(struct file *, int, struct file_lock*); 
    ssize_t(*readv)(struct file *, const struct iovec *, unsigned long, loff_t*); 
    ssize_t(*writev)(struct file *, const struct iovec *, unsigned long, loff_t*);       // readv 和 writev:分散/聚集型的读写操作 
    ssize_t(*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void*);       // 通常为 NULL 
    ssize_t(*sendpage)(struct file *, struct page *, int, size_t, loff_t *, int);       // 通常为 NULL 
    unsigned long(*get_unmapped_area)(struct file *,unsigned long, unsigned long,unsigned long, unsigned long);   // 在进程地址空间找到一个将底层设备中的内存段映射的位置 
    int(*check_flags)(int);     // 允许模块检查传递给 fcntl(F_SETEL...)调用的标志 
    int(*dir_notify)(struct file *filp, unsigned long arg);     // 仅对文件系统有效,驱动程序不必实现 53   int(*flock)(struct file *, int, struct file_lock*);  
};

 修改模块代码

//添加头文件
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

static int mymodule_open(struct inode *inode,struct file *filp)
{
    return 0;
}

static int mymodule_release(struct inode *inode,struct file *filp)
{
    return 0;
}

//*ppos 是要读写的位置对于读写文件开头的偏移
static ssize_t mymodule_read(struct file *filp,char __ user *buf,size_t count,loff_t *ppos)
{
    unsigned long p = *ppos;
    int ret = 0;
    return ret;
}

static ssize_t mymodule_write(struct file *filp,const char __user *buf,size_t count,loff_t *ppos)
{
    unsigned long p = *ppos;
    int ret = 0;
    return ret;
}

//file_operation设备驱动文件操作结构体
static struct file_operations mymodule_fops = {
    .owner = THIS_MODULE,
    .open = mymodule_open,
    .release = mymodule_release,
    .read = mymodule_read,
    .write = mymodule_write,
};

//模块加载
int __init mymodule_init(void)
{
    return 0;
}
//模块卸载
void __exit mymodule_exit(void)
{
    
}

module_init(mymodule_init);
module_exit(mymodule_exit);
MODULE_AUTHOR("HaoRan");
MODULE_LICENSE(“GPL”);

 初始化并添加cdev结构体

cdev结构体描述字符设备,结构体的定义代码如下

struct cdev{
    struct kobject kob; //内嵌kobject对象
    struct module *owner; //所属模块
    struct file_operations *ops; //文件操作结构体
    struct list_head list;
    dev_t dev;  //设备号
    unsigned int count;
};

cdev_t 成员定义了设备号,为32位,其中高12位为主设备号,底20位为次设备号。使用宏 MAJOR(dev_t dev) 从dev_t获取主设备号,MINOR(dev_t dev) 获取次设备号。   使用宏 MKDEV(int major,int minor) 通过主设备号和次设备号生成dev_t。

内核提供的cdev结构体的操作函数有

//cdev_init用于初始化cdev成员,并建立cdev和file_operation之间的关系
void cdev_init(struct cdev *cdev,struct file_operation *fops)
{
    memset(cdev,0,sizeof *cdev);
    INIT_LIST_HEAD(&cdev->list);
    cdev->kobj.ktype = &ktype_cdev_default;
    kobject_init(&cdev->kobj);
    cdev->ops = fops;
}

//cdev_alloc用于动态申请一个cdev内存
struct cdev *cdev_alloc(void)
{
    struct cdev *p = kmalloc(sizeof(struct cdev),GFP_KERNEL);
    if(p){
        menset(p,0,sizeof(struct cdev));
        p->kobj.ktype = &ktype_cdev_dynamic;
        INIT_LIST_HEAD(&p->list);
        kobject_init(&p->kobj);
    }
    return p;
}

//cdev_add()和cdev_del() 函数分别向系统添加和删除一个cdev,完成字符设备的注册和注销,通常出现在字符设备的注册和注销函数中。

在调用cdev_add函数向系统注册字符设备之前,应先调用 register_chrdev_region() 或 alloc_chedev_region() 或 register_chrdev() 函数向系统申请设备号。调用cdev_del函数之后,还需调用unsigned_chadev() 或 unsignred_chadev_region() 释放之前申请的设备号。

  • extern int register_chrdev_region(dev_t, unsigned, const char *); //静态的申请和注册设备号
  • extern int  alloc_chrdev_region(dev_t, unsigned, const char *);     //动态的申请注册一个设备号
  • extern int register_chrdev(unsigned int, const char *,struct file_operations *);//int为0时候动态注册,非零时候静态注册。
  • extern int unregister_chrdev(unsigned int, const char *);   //注销设备号
  • extern void unregister_chrdev_region(dev_t, unsigned);   //注销设备号

修改之前的模块的加载和卸载函数

//添加头文件
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <linux/slab.h>
#include <asm/uaccess.h>

static int major=0,minor=0;

//定义cdev结构体
struct mymodule_dev{
    struct cdev cdev;
    unsigned char buf[512];
};

struct mymodule_dev *mydev;

static int mymodule_open(struct inode *inode,struct file *filp)
{
    filp->private_data = mydev;
    return 0;
}

static int mymodule_release(struct inode *inode,struct file *filp)
{
    return 0;
}

static ssize_t mymodule_read(struct file *filp,char __user *buf,size_t count,loff_t *ppos)
{
    unsigned long p = *ppos;
    int ret = 0;
    struct mymodule_dev *dev = filp->private_data;
    if(copy_to_user(buf,dev->buf,count))
        return -EFAULT;
    else
    {
        *ppos += count;
        ret = count;
        printk(KERN_INFO"read %d bytes from %ld\n",count,p);
    }
    return ret;
}

static ssize_t mymodule_write(struct file *filp,const char __user *buf,size_t count,loff_t *ppos)
{
    unsigned long p = *ppos;
    int ret = 0;
    struct mymodule_dev *dev = filp->private_data;
    if(copy_from_user(dev->buf,buf,count))
        return -EFAULT;
    else
    {
        *ppos += count;
        ret = count;
        printk(KERN_INFO"write %d bytes from %ld\n",count,p);
    }
    return ret;
}

//file_operation设备驱动文件操作结构体
static struct file_operations mymodule_fops = {
    .owner = THIS_MODULE,
    .open = mymodule_open,
    .release = mymodule_release,
    .read = mymodule_read,
    .write = mymodule_write,
};

//初始化并添加cdev结构体
static void mymodule_cdev_setup(struct mymodule_dev *dev)
{
    int err,devno=MKDEV(major,minor);
    //初始化
    cdev_init(&dev->cdev,&mymodule_fops);
    dev->cdev.owner = THIS_MODULE;
    dev->cdev.ops = &mymodule_fops;
    //注册,添加
    err = cdev_add(&dev->cdev,devno,1);
    if(err)
        printk(KERN_NOTICE"error %d adding mymodule",err);
}

//模块加载
int __init mymodule_init(void)
{
    //申请设备号
    int result;
    dev_t devno = MKDEV(major,minor);
    if(major)
        result = register_chrdev_region(devno,1,"mymodule");
    else{
        result = alloc_chrdev_region(&devno,minor,1,"mymodule");
        major = MAJOR(devno);
        minor = MINOR(devno);
    }
    if(result<0)
        return result;
    //动态申请设备结构体内存
    mydev = kmalloc(sizeof(struct mymodule_dev),GFP_KERNEL);
    if(!mydev){
        result=-ENOMEM;
        goto fail_malloc;
    }
    memset(mydev,0,sizeof(struct mymodule_dev));
    //cdev字符设备的初始化和添加
    mymodule_cdev_setup(mydev);
    return 0;
fail_malloc:unregister_chrdev_region(devno,1);
return result;  
}
//模块卸载
void __exit mymodule_exit(void)
{
    //删除cdev结构体
    cdev_del(&mydev->cdev);
    kfree(mydev);
    //注销设备号
    unregister_chrdev_region(MKDEV(major,minor),1);
}

module_init(mymodule_init);
module_exit(mymodule_exit);
MODULE_AUTHOR("HaoRan");
MODULE_LICENSE("GPL");

添加到上一讲的Makefile中编译

将生成的ko文件复制到文件系统,insmod

 然后使用 cat 命令查看驱动加载的情况和设备号

然后使用 mknod 创建设备节点

mknod /dev/mynodule c 252 0

写测试代码module_tese.c

#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
int main()
{
    char buf[50];
    int fd = open("/dev/mymodules",O_RDWR);
    write(fd,"hello world",sizeof("hello world"));
    sleep(2);
    read(fd,buf,strlen(buf));
    for(int i=0;i<strlen(buf);i++)
        printf("%c",buf[i]);
    printf("\n");
    close(fd);
    return 0;
}

交叉编译,在qemu执行

 

对于设备节点的注册,也可以在代码中自动注册,注册代码为

//定义和创建设备类
struct class *my_class;
my_class = class_create(THIS_MODULE,"设备名称");
//定义和创建设备节点
struct device *my_device;
my_device = device_create(struct class *my_class,NULL,MKDEV(major,minor),NULL,"设备节点名");
//删除设备节点
device_destroy(struct class *my_class,MKDEV(major,minor));
//销毁设备类
class_destroy(struct class *my_class)

//头文件为  #include <linux/device.h>

修改后代码为

//添加头文件
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <linux/device.h>

static int major=0,minor=0;

//定义cdev结构体
struct mymodule_dev{
    struct cdev cdev;
    unsigned char buf[512];
};

struct mymodule_dev *mydev;
struct class *my_class;
struct device *my_device;

static int mymodule_open(struct inode *inode,struct file *filp)
{
    filp->private_data = mydev;
    return 0;
}

static int mymodule_release(struct inode *inode,struct file *filp)
{
    return 0;
}

static ssize_t mymodule_read(struct file *filp,char __user *buf,size_t size,loff_t *ppos)
{
    unsigned long p = *ppos;
    unsigned int count = size;
    int ret = 0;
    struct mymodule_dev *dev = filp->private_data;
    if(copy_to_user(buf,dev->buf,count))
        return -EFAULT;
    else
    {
        *ppos += count;
        ret = count;
        printk(KERN_INFO"read %d bytes from %ld\n",count,p);
    }
    return ret;
}

static ssize_t mymodule_write(struct file *filp,const char __user *buf,size_t size,loff_t *ppos)
{
    unsigned long p = *ppos;
    unsigned int count = size;
    int ret = 0;
    struct mymodule_dev *dev = filp->private_data;
    if(copy_from_user(dev->buf,buf,count))
        return -EFAULT;
    else
    {
        *ppos += count;
        ret = count;
        printk(KERN_INFO"write %d bytes from %ld\n",count,p);
    }
    return ret;
}

//file_operation设备驱动文件操作结构体
static struct file_operations mymodule_fops = {
    .owner = THIS_MODULE,
    .open = mymodule_open,
    .release = mymodule_release,
    .read = mymodule_read,
    .write = mymodule_write,
};

//初始化并添加cdev结构体
static void mymodule_cdev_setup(struct mymodule_dev *dev)
{
    int err,devno=MKDEV(major,minor);
    //初始化
    cdev_init(&dev->cdev,&mymodule_fops);
    dev->cdev.owner = THIS_MODULE;
    dev->cdev.ops = &mymodule_fops;
    //注册,添加
    err = cdev_add(&dev->cdev,devno,1);
    if(err)
        printk(KERN_NOTICE"error %d adding mymodule",err);
}

//模块加载
int __init mymodule_init(void)
{
    //申请设备号
    int result;
    dev_t devno = MKDEV(major,minor);
    if(major)
        result = register_chrdev_region(devno,1,"mymodule");
    else{
        result = alloc_chrdev_region(&devno,minor,1,"mymodule");
        major = MAJOR(devno);
        minor = MINOR(devno);
    }
    if(result<0)
        return result;
    //动态申请设备结构体内存
    mydev = kmalloc(sizeof(struct mymodule_dev),GFP_KERNEL);
    if(!mydev){
        result=-ENOMEM;
        goto fail_malloc;
    }
    memset(mydev,0,sizeof(struct mymodule_dev));
    //cdev字符设备的初始化和添加
    mymodule_cdev_setup(mydev);

    //注册设备节点
    my_class = class_create(THIS_MODULE,"mymodule_t");
    my_device = device_create(my_class,NULL,MKDEV(major,minor),NULL,"mymodules");

    return 0;
fail_malloc:unregister_chrdev_region(devno,1);
return result;  
}
//模块卸载
void __exit mymodule_exit(void)
{
    device_destroy(my_class,MKDEV(major,minor));
    class_destroy(my_class);
    //删除cdev结构体
    cdev_del(&mydev->cdev);
    kfree(mydev);
    //注销设备号
    unregister_chrdev_region(MKDEV(major,minor),1);
}

module_init(mymodule_init);
module_exit(mymodule_exit);
MODULE_AUTHOR("HaoRan");
MODULE_LICENSE("GPL");

编译加载,其中的mymodule_t会以设备类的形式出现在/sys/class下

而mymodules作为设备节点出现在/dev下

在file_operation相关函数中的 inode 结构体记录了文件系统中文件的相关信息,包括文件的大小、创建者、创建日期等,我们称之他为“索引节点”,每个文件都有对应的inode,也就是说inode表示具体的文件。inode结构体中还包含了设备文件的主从设备号,可以用宏  unsigned int iminor(struct inode *inode)和 unsigned int imajor(struct inode *inode)获取主从设备号。

file 结构体是一个内核结构,代表一个打开的文件(文件描述符),在open时它由内核创建,并传递给file_operations所指向的各个函数,当文件关闭后给结构体被释放。该结构体记录了文件的读写模式、文件当前读写位置、还有file_operations结构体的指针等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值