字符设备驱动有关(二)

理论分析在上一篇日志中写完了。在这篇写了简单的字符驱动程序,交叉编译后在开发板OK6410上运行。

建立char_simple.c程序:(程序中的printk中最好是用英文,不然在minicom中显示乱码)

下面程序中只要是包含char_simple*是自己取的名字,可以随意改。


#include<linux/module.h>//模块
#include<linux/init.h>//初始化和清除
#include<linux/errno.h>//错误
#include<linux/fs.h>//文件系统file_operations
#include<asm/uaccess.h>//copy_to_user,copy_from_user
#include<linux/kernel.h>//printk
#include<linux/device.h>//创建动态设备文件device,class
#include<linux/cdev.h>//cdev结构
#include<linux/types.h>//sizeof
MODULE_LICENSE("GPL");//必须的不然编译成功但装载模块时会出错


//声明,当然,你要是先实现这些函数就不用声明了
static int char_simple_open(struct inode *innode,struct file *filp);//后面有具体实现
static int char_simple_release(struct inode *inode,struct file *filp);
static ssize_t char_simple_read(struct file *filp,char __user *buf,size_t count,loff_t *f_pos);
static ssize_t char_simple_write(struct file *filp,const char __user *buf,size_t count,loff_t *f_pos);

//定义变量
char char_simple_buffer[1024];  //read和write时用到,内核空间
dev_t dev;                              //alloc_chrdev_region时用到,包含设备号
struct cdev cdev;                     //cdev_init时用到
struct class *char_simple_class;//class_creat时用到

//file_operations 数据结构,必须放到static int char_simple_init(void)前面,因为cdev_init会用到

//由于没有定义llseek,所以在后面的测试时,write后read其偏移量会出现问题

struct file_operations char_simple_fops=
{
    .owner=THIS_MODULE,
    .read=char_simple_read,//可以随便修改名字,但要与后面对应起来
    .write=char_simple_write,
    .open=char_simple_open,
    .release=char_simple_release,
};

//模块初始化加载函数
static int char_simple_init(void)//也可以static int __int char_simple_int(void) 
{
    int result;
    /*动态分配设备号*/
    result=alloc_chrdev_region(&dev,0,1,"char_simple");
    if(result<0){//检查是否分配成功
        printk(KERN_INFO"alloc_chrdev_region error!\n");
        return -ENODEV;
    }

    printk(KERN_INFO"major:%d,minor:%d",MAJOR(dev),MINOR(dev));//看看设备号是多少

    /*cdev初始化*/
    cdev_init(&cdev,&char_simple_fops);
    cdev.owner=THIS_MODULE;
    cdev.ops=&char_simple_fops;

    /*添加cdev到内核*/
    result=cdev_add(&cdev,dev,1);//成功返回0,失败-1
    if(result){//检测是否添加成功,等同于if(result!=0)
        unregister_chrdev_region(dev,1);//不成功直接取消设备号,这个狠。
        printk(KERN_INFO"cdev_add error:error no:%d\n",result);
        return -EINVAL;
    }

    /*动态分配节点,也就是设备文件*/
    char_simple_class=class_create(THIS_MODULE,"char_simple");
    if(!char_simple_class){//判断创建类是否成功,等同于if(char_simple_class==0)
        printk(KERN_INFO"class_creat error\n");
        unregister_chrdev_region(dev,1);
        unregister_chrdev(MAJOR(dev),"char_simple");//这是老方法时用的卸载,可以注释掉
        cdev_del(&cdev);//这是对应上面cdev_add用的卸载
        return -EINVAL;
    }
    device_create(char_simple_class,NULL,dev,NULL,"char_simple");
    return 0;
}


//模块卸载函数
static void char_simple_exit(void)
{
    unregister_chrdev_region(dev,1);//移除设备号
    
    device_destroy(char_simple_class,dev);//移除设备文件

    class_destroy(char_simple_class);//移除类

    cdev_del(&cdev);//从内核中取消注册

    unregister_chrdev(MAJOR(dev),"char_simple");//用register_chardev在内核注册时用这个删除,是老方法,不建议用了。卸载时会多出提示rmmod: module 'char_simple' not found。但实际已经卸载掉了。卸载后用lsmod查看到底有没有卸载掉。
}


//声明初始化和卸载
module_init(char_simple_init);
module_exit(char_simple_exit); 


/*到现在为止就完成了上一篇日志中第一、二步*/

//open实现
static int char_simple_open(struct inode *innode,struct file *filp)
{
    /*可以直接return 0,其它不用写,此文件打开操作永远成功,但系统不会通知驱动程序还可以用container_of宏来将包含cdev的dev存在file数据结构中的private_data中,这样当有多个设备时,指定各自对应的cdev,本例中没有采用这种方式如果采用,则在后面read,write中,可以不使用char char_simple_buffer[1024],而是用结构dev中的dev->data来指定; 我还没试过怎么用啊,不过这种方法感觉挺好的*/
    printk(KERN_INFO"call open\n");//注意KERN_INFO,这样在测试时会提示“call open”
    //MOD_INC_USE_COUNT;//2.4老版本内核,增加模块使用计数,防止模块在使用时被卸载掉
    try_module_get(THIS_MODULE);//新方法,貌似现在系统自己计算了,也没什么用
    return 0;
}


//release实现
static int char_simple_release(struct inode *inode,struct file *filp)
{
    printk(KERN_INFO"call release\n");//KERN_INFO级别具体查看printk吧
    //MOD_DEC_USE_COUNT;
    module_put(THIS_MODULE);//同上
    return 0;
}



//read实现,copy_to_user

/*

这里很难懂啊,也可能是我个人觉得,我自己理下:

在用户环境中查看man 2 read :ssize_t read(int fd, void *buf, size_t count),注意和下面比较好懂点。

驱动中,自己定义的char_simple_buffer[1024],是在内核空间,貌似可以用kmalloc申请动态空间。

copy_to_user(to,from,n),to是到用户空间,也就是buf;from是从内核空间,也就是char_simple_buffer

f_pos是在结构体file中的,当前这个设备文件的读/写位置。从此处开始读/写

内核空间:         &&&&&&&&&&

文件偏移量:      &&&&&& * &&&           (*处开始读写) *可以超过,空洞文件

count如果是:                  &&&&&&,就会超出两个& 
*/

static ssize_t char_simple_read(struct file *filp,char __user *buf,size_t count,loff_t *f_pos)
{
    printk(KERN_INFO"call read\n");
    if(*f_pos>=sizeof(char_simple_buffer)){//如果偏移量大于等于内核空间容量,溢出,但是为什么会溢出呢
        printk(KERN_INFO"read buffer overflow\n");//我在想一开始现有个内核空间,read/write使偏移量增加
        *f_pos=sizeof(char_simple_buffer);//然后我释放掉这个空间,再建一个空间,而f_pos不变,就溢出了
        return 0;                                    //自己瞎想的,不知道???
    }
    if((count+*f_pos)>sizeof(char_simple_buffer)){//count是传入的数据长度,参考上面&&描述
        count=sizeof(char_simple_buffer)-*f_pos;
    }
    if(copy_to_user(buf,&char_simple_buffer[*f_pos],count)){//copy成功返回0
        printk(KERN_INFO"copy_to_user error\n");//不成功返回copy成功的字节数
        return -EFAULT;                                    //这里直接不成功出错
    }else{
        *f_pos=*f_pos+count;                 //记录read后的文件偏移量
        printk(KERN_INFO"read:%dbytes\n",count);
    }
    return count;//注意return值和open的比较下
}


//write实现,copy_from_user,同read解释
static ssize_t char_simple_write(struct file *filp,const char __user *buf,size_t count,loff_t *f_pos)
{
    printk(KERN_INFO"call write\n");
     if(*f_pos>=sizeof(char_simple_buffer)){
//*f_pos是用户文件位置
        printk(KERN_INFO"write buffer overflow\n");
        *f_pos=sizeof(char_simple_buffer);
        return 0;
    }
    if((count+*f_pos)>sizeof(char_simple_buffer)){//count是传入的数据长度
        count=sizeof(char_simple_buffer)-*f_pos;
    }
    if(copy_from_user(&char_simple_buffer[*f_pos],buf,count)){//同read
        printk(KERN_INFO"copy_from_user error\n");
        return -EFAULT;
    }else{
        *f_pos=*f_pos+count;
        printk(KERN_INFO"write:%dbytes\n",count);
    }
    return count;//注意这个
}
--------------------------------------------------------------------------------------------------------------
好了,以上就写完了。然后写Makefile文件,参考上上上篇helloworld模块的日志,改下里面的名字就好了

然后make下,生成char_simple.ko文件,传到OK6410开发板。insmod装载,lsmod查看,rmmod卸载。


下面来测试一下,是否可以。这个测试程序write和read用同一个buff,会有些冲突,懒得改了,下一篇改。

测试程序char_simple_test.c

#include<sys/types.h>
#include<sys/stat.h>
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>

int main()
{
    int fd,size;       //fd文件描述符
    char buff[128];//是用户空间,相当于驱动程序中的buf
    if((fd=open("/dev/char_simple",2))==-1){//char_simple要根据你自己的名字来定,在驱动中下面定义的
        printf("open error!\n");//在device_create(char_simple_class,NULL,dev,NULL,"char_simple");
        exit(1);
    }
    
    if((size=read(fd,buff,strlen(buff)))<0){
//read第一次
        printf("read error!\n");
        exit(1);
    }
    printf("read:%dbytes\n",size);//在驱动中printk(KERN_INFO"read:%dbytes\n",count);所以在这可以不写
    printf("R:%s\n",buff);

    printf("please input:\n");//write同read
    scanf("%s",&buff);        //随便输入几个字符
    if((size=write(fd,buff,strlen(buff)))!=strlen(buff)){
        printf("write error!\n");
        exit(1);
    }
    printf("W:%s\n",buff);

    /*close(fd);
    if((fd=open("/dev/char_simple",2))==-1){
        printf("open error!\n");
        exit(1);
    }*/  //重新打开,重置偏移量,没成功
    
    if((size=read(fd,buff,strlen(buff)))<0){//read第二次
        printf("read error!\n");
        exit(1);
    }
    printf("read:%dbytes\n",size);
    printf("R:%s\n",buff);
    
    close(fd);//关闭文件
    exit(0);

}
----------------------------------------------------------------------------------------

交叉编译arm-linux-gcc 下。将生成的char_simple_test传入开发板中运行。可以看结果了。

可以编译成功,但由于f_ops文件偏移量没法处理,write后接着read时,read会读write后的字符。

当然了,这个测试程序可以在PClinux测试,新建test.txt,当作设备文件。open时加入O_TRUNC打开,或者lseek,或者close后再open来实现字符偏移量的重置。

----------------------------------------------------------------------------------------

终于完成了,当然上面的解释并不一定准确,还需多多学习。

还是应该试验下container_of,dev结构什么的。还有吐槽下LDD3里面的量子集什么的看不懂啊,也没完整程序,整这么复杂,郁闷。

还是要多写下啊,不然不参考书一句都写不出来。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值