驱动篇-字符驱动入门(完美解决cat echo 字符设备乱码的问题)(一)

闲来无事,整理一下驱动入门知识!
大部分与网上整理的差不多,我主要想说的有两个特别的地方,刚入门的人看别人整理的肯定都不知道怎么测试。或者测试结果不像他们所写的那样!
第一点就是用mknod创建的设备名,设备号不能随便写,必须你所写的源文件命名的一致。
比如你在c文件中定义

#define DEV_NAME "chardev"

那么设备名就是chardev
设备号可以通过 cat /proc/devices |grep chardev 得到主设备号。
root@lll-virtual-machine:/home/lll/share/linuxdev/chardev# cat /proc/devices |grep chardev
249 chardev
第二点要注意的就是通过echo 或cat来直接操作 /dev/chardev字符文件不能得到正确的内容。使用命令:
echo “123”>/dev/glaobalvar
向字符设备写入命令,然后使用cat命令:
cat /dev/globalvar
读取字符数据的时候,出现了无限输出的情况.
原因
首先,read()函数的返回值设定不正确,
static ssize_t chardev_read(struct file *filp, char *buf, size_t len, loff_t *off)
{
……
return sizeof(int); //此处不应该是一个固定数值,如果是固定值,在终端中进行cat 时,系统默认当前终端为独立的一个进程,读取未结束,会一直/反复的读取这里面内容,
}
cat的内部大概是这样
while (1) {
int n = read(0, buf, size);
if (n <= 0) break;
write(1, buf, n);
}
所以,要让cat退出,必须保证后续的read能够返回0(即读到EOF)。
文件偏移指针,也就是fops->read的第四个参数,loff_t *off,就是起这个作用的。
如果*off >= sizeof(int),就可以直接返回0了(表示EOF)。

下面的代码是cat echo 会乱码。可以先看看对比一下

–> 这是chardev.c 文件
1. 字符驱动大体也有一个模板。建立mychar.c的代码如下,

#include <linux/init.h> //指定初始化和清除函数
#include <linux/module.h> //模块所需的大量符号和函数定义
#include <linux/kernel.h> //内核所需要的函数 
#include <linux/fs.h> //文件系统相关的函数和头文件
#include <asm/uaccess.h> //在内核和用户空间中移动数据的函数
#include <linux/cdev.h> //cdev结构的头文件

MODULE_LICENSE("GPL");
MODULE_AUTHOR("lll");

#define DEV_NAME "chardev"

static ssize_t chardevRead(struct file *,char *,size_t,loff_t*);
static ssize_t chardevWrite(struct file *,const char *,size_t,loff_t*);

static int char_major = 0;
static int chardevData = 12345; //"chardev" device’s global variable.

//initialize char device’s file_operations struct
struct file_operations chardev_fops =
{
    .read = chardevRead,
    .write = chardevWrite 
};

static int __init chardev_init(void)
{
    int ret;
    ret = register_chrdev(char_major,DEV_NAME,&chardev_fops);

    if(ret<0) 
    {
        printk(KERN_ALERT "chardev Reg Fail!\n");
    } 
    else
    {
        printk(KERN_ALERT "chardev Reg Success!\n");
        char_major = ret;
        printk(KERN_ALERT "Major = %d\n",char_major);
    }
    return 0;
}

static void __exit chardev_exit(void)
{
    unregister_chrdev(char_major,DEV_NAME);
    printk(KERN_ALERT "chardev is dead now!\n");
    return;
}

static ssize_t chardevRead(struct file *filp,char *buf,size_t len,loff_t *off)
{
    printk("chardev is read now!\n");
    chardevData -= 1;
    if (copy_to_user(buf,&chardevData,sizeof(int))) 

    {
        return -EFAULT;
    }
    printk("chardev is read: buf=%d\n",*buf);
    printk("chardev is read end: len=%d\n",len);

    return *buf;
}

static ssize_t chardevWrite(struct file *filp,const char *buf,size_t len,loff_t *off)
{
    printk("chardev is write now!\n");
    printk("chardev is write: buf=%d\n",*buf);

    if (copy_from_user(&chardevData,buf,sizeof(int))) 
    {
        return -EFAULT;
    }
    printk("chardev is write end: len=%s\n",len);
    return *buf;
}

module_init(chardev_init);
module_exit(chardev_exit);

–>下面是Makefile文件
root@lll-virtual-machine:/home/lll/share/linuxdev/chardev# cat Makefile


obj-m:=chardev.o
testmodule-objs:=module KDIR:=/lib/modules/3.11.0-15-generic/build #这是地方每个人的系统不一样,请不要照搬。 #KDIR=/lib/modules/$(shell uname -r)/build #可以写成这样哈 MAKE:=make default: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules clean: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) clean rmmod chardev rm -f /dev/chardev

上面两个文件都有了之后。直接make 生成chardev.ko
–>然后insmod chardev.ko 可以通过dmesg|tail -n 10 看到log
–>然后cat /proc/devices |grep chardev 查看设备号
root@lll-virtual-machine:/home/lll/share/linuxdev/chardev# cat /proc/devices |grep chardev
249 chardev

–>mknod -m 666 /dev/chardev 249 0
下面还得通过应用层的app来操作这个设备。

//root@lll-virtual-machine:/home/lll/share/linuxdev/chardev# cat testchardev.c
#include <stdio.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#define DEV_NAME "/dev/chardev" 

int main(void)  
{  
     int fd;  
     int num=9999;  
     char buf1[10] = {0, 1};  
     char buf2[10];  

     fd = open(DEV_NAME,O_RDWR);
     if(fd < 0)  
     {  
         printf("Open /dev/mychar error\n");  
         return -1;  
     }  

    read(fd,&num,sizeof(int));
    printf("The myChar is %d\n",num);

    printf("Please input a number written to myChar: ");
    scanf("%d",&num);

    write(fd,&num,sizeof(int));

    read(fd,&num,sizeof(int));
    printf("The myChar you input is %d\n",num);

    close(fd);
    return 0;  
  }  

–> gcc testchardev.c -o test 生成应用
./tes t
root@lll-virtual-machine:/home/lll/share/linuxdev/chardev# ./test
The myChar is 926298623
Please input a number written to myChar: 35
The myChar you input is 34

root@lll-virtual-machine:/home/lll/share/linuxdev/chardev# ./test
The myChar is 33
Please input a number written to myChar: 23
The myChar you input is 22

—->下面使用echo cat 测试 结果如下。可以看到出来的结果并不正确!
root@lll-virtual-machine:/home/lll/share/linuxdev/chardev# echo “2” >/dev/chardev
root@lll-virtual-machine:/home/lll/share/linuxdev/chardev# cat /dev/chardev
1
670
67/
67.
67-
67,
67+
67*
67)
67(
67’
67&
67%
67$
67#
67”
67!
67
67
67
67
67
67
67
67
67
67
67
67
67
67
67
67
67
67
67
67
67

67

67

67
67
67
67
67
67
67
6

下面是修改后完美解决cat echo 字符设备乱码的问题的代码

root@lll-virtual-machine:/home/lll/share/linuxdev/chardev# cat mychar.c 
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/uaccess.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("lll");

#define DEV_NAME "chardev"
#define DEV_SIZE 0x400 //定义字符设备的大小,400字节

static ssize_t chardevRead(struct file *,char *,size_t,loff_t*);
static ssize_t chardevWrite(struct file *,const char *,size_t,loff_t*);

static int char_major = 0;
static char  chardevData[1024] = "hello world\n"; //"chardev" device’s global variable.

//initialize char device’s file_operations struct
struct file_operations chardev_fops =
{

    .read = chardevRead,
    .write = chardevWrite 

};


static int __init chardev_init(void)
{

        printk(KERN_ALERT "=====================init====================\n");
    int ret;
    ret = register_chrdev(char_major,DEV_NAME,&chardev_fops);

    if(ret<0) 
    {
            printk(KERN_ALERT "chardev Reg Fail!\n");

    } 
    else
    {
        printk(KERN_ALERT "chardev Reg Success!\n");
            char_major = ret;
            printk(KERN_ALERT "Major = %d\n",char_major);
        }
        return 0;
}

static void __exit chardev_exit(void)
{

        unregister_chrdev(char_major,DEV_NAME);
        printk(KERN_ALERT "chardev is dead now!\n");

        return;
}

static ssize_t chardevRead(struct file *filp,char *buf,size_t len,loff_t *off)
{

        printk(KERN_ALERT "-------------------Read------------------\n");
        unsigned long offset =  *off;
        unsigned int count = len; 
        int ret = 0;
        /*分析和获取有效的写长度*/
        if (offset > DEV_SIZE)
        {
        printk("offset > DEV_SIZE\n");
            return count ?  - ENXIO: 0;
        }
        else if(offset == DEV_SIZE)
        {
        printk("offset = DEV_SIZE\n");
            return 0;   // 防止测试cat /dev/chardev 时 文件尾出现错误提示
        }
        if (count > DEV_SIZE - offset)
        {
        printk("count > DEV_SIZE -offset\n");
            count = DEV_SIZE - offset;
        }

        printk("read char_len=%ld\n",strlen(chardevData));
        printk("read chardevData=%s\n",chardevData);
        /* char *ps;int i; //strncpy(ps,chardevData,sizeof(chardevData)); ps = chardevData; printk("read ps=%s\n",ps); for(i=0; ps[i] !='\0'; i++) if(ps[i]=='\0'){ printk("read ps[i]=%s\n",ps+i); printk("There is a '0' in the string!\n"); return 0; } else { printk("There is not '0' in the string!\n"); printk("read ps[i]=%s\n",ps+i); */ 
    if (!copy_to_user(buf,(char*)(chardevData), strlen(chardevData)+1))
        {
        *off += count;
            printk(KERN_INFO "read %d bytes(s) from %ld addr\n", count, offset);
            ret = count;
        }
        else
            return -EFAULT;
//}
        printk("chardev is read: buf=%s\n",buf);
        printk("chardev is read: ref=%d\n",ret);

    return ret;
}

static ssize_t chardevWrite(struct file *filp,const char *buf,size_t len,loff_t *off)
{
        printk(KERN_ALERT "--------Write-------------\n");
        printk("chardev is write now!\n");
        printk("chardev is write: buf=%s\n",buf);
        unsigned long offset =  *off;
    offset=0;
        unsigned int count = len; 
        int ret = 0;
        memset(chardevData,0,strlen(chardevData));
        /*分析和获取有效的写长度*/
        if (offset > DEV_SIZE)
        {
        printk("offset > DEV_SIZE\n");
            return count ?  - ENXIO: 0;
        }
        else if(offset == DEV_SIZE)
        {
        printk("offset = DEV_SIZE\n");
            return 0;   // 防止测试echo /dev/chardev 时 文件尾出现错误提示
        }
        if (count > DEV_SIZE - offset)
        {
            count = DEV_SIZE - offset;
        }
        printk("count= %d\n",count);
        printk("need len:%ld, offset:%ld\n",len,offset);

        if (copy_from_user((char*)(chardevData),buf,count))
        {
            printk("copy error! chardevData=%s\n",chardevData);
            return -EFAULT;
        }
    else
    {
        *off += count;
            printk(KERN_INFO "read %d bytes(s) from %ld addr\n", count, offset);
            ret = count;
    }
        printk("chardevData=%s\n",chardevData);

        return ret;
}

module_init(chardev_init);
module_exit(chardev_exit);

补充资料:

二.主设备号

字符设备通过字符设备文件来存取。字符设备文件由使用ls -l 的输出的第一列的”C”标识;
如果使用ls –l 命令,会看到在设备文件项中有2个数(由一个逗号分隔)这些数字就是设备文件的主次设备编
1>.主设备号用来标识与设备文件相连的驱动程序。
次设备号被驱动程序用来辨别操作的是哪个设备。
主设备号用来反映设备类型*
次设备号用来区分同类型的哪一个设备
内核中如何描述设备号?
dev_t 其实质为unsigned int 32位整数,其中高12位为主设备号,低20位为次设备号。
安装驱动后,从/proc/devices中查询设备号

字符设备驱动操作接口

在编写Linux内核设备驱动时,需要使用内核提供的设备驱动接口,向内核提供具体设备的操作方法。应用程序可以像操作普通文件一样操作字符设备。常见的串口、调制解调器都是字符设备。编写字符设备驱动需要使用内核提供的(1)**register_chrdev()**函数注册一个字符设备驱动。函数定义如下:

int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);

参数major是主设备号,name是设备名称,fops是指向函数指针数组的结构指针,驱动程序的入口函数都是包括在这个指针内部。该函数的返回值如果小于0表示注册设备驱动失败,如果设置major为0,表示由内核动态分配主设备号,函数返回值是主设备号。

当用register_chrdev()函数成功注册一个字符设备后,会在/proc/devices文件中显示出设备信息

(2) register_chrdev()函数中有一个fops参数,该参数指向一个file_operations结构,该结构包含了驱动上所有操作。随着内核功能的不断增加,file_operations结构的定义也越来越复杂,内核2.6.18版本的定义如下:

struct file_operations

{
struct module *owner;
loff_t (llseek) (struct file , loff_t, int);
ssize_t (read) (struct file , char __user , size_t, loff_t );
ssize_t (write) (struct file , const char __user , size_t, loff_t );
ssize_t (aio_read) (struct kiocb , const struct iovec *, unsigned long, loff_t);
ssize_t (aio_write) (struct kiocb , const struct iovec *, unsigned long, loff_t);
int (readdir) (struct file , void *, filldir_t);
unsigned int (poll) (struct file , struct poll_table_struct *);
int (ioctl) (struct inode , struct file *, unsigned int, unsigned long);
long (unlocked_ioctl) (struct file , unsigned int, unsigned long);
long (compat_ioctl) (struct file , unsigned int, unsigned long);
int (mmap) (struct file , struct vm_area_struct *);
int (open) (struct inode , struct file *);
int (flush) (struct file , fl_owner_t id);
int (release) (struct inode , struct file *);
int (fsync) (struct file , struct dentry *, int datasync);
int (aio_fsync) (struct kiocb , int datasync);
int (fasync) (int, struct file , int);

};

结构中每个成员都是一个函数指针,指向不同功能的函数,大部分驱动都没有提供所有的函数。对于字符设备来说,常用的成员及解释如下:
**int (*open) (struct inode *, struct file *);**
尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知。
**ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);**
用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以-EINVAL(”Invalid argument”) 失败. 一个非负返回值代表了成功读取的字节数( 返回值是一个 “signed size” 类型, 常常是目标平台本地的整数类型)。
**ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);**
发送数据给设备. 如果 NULL, -EINVAL 返回给调用write 系统调用的程序. 如果非负, 返回值代表成功写的字节数。
**int (*release) (struct inode *, struct file *);**
在文件结构被释放时引用这个操作. 如同 open, release 可以为 NULL。
**int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);**
ioctl 系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不是写)。另外, 几个ioctl 命令被内核识别而不必引用 fops 表。如果设备不提供ioctl 方法, 对于任何未事先定义的请求(-ENOTTY,”设备无这样的ioctl”), 系统调用返回一个错误。
(3)与注册相反,内核提供了unregister_chrdev()函数卸载设备驱动,定义如下:
**int unregister_chrdev(unsigned int major, const char *name);**
major是想要卸载的主设备号,name是欲卸载的设备驱动名称。内核会比较设备驱动名称和设备号是否相同,不同则返回-EINVAL。错误地卸载设备驱动可能会带来严重后果,因此在卸载驱动的时候应该对函数返回值进行判断。

你可能感兴趣的:(驱动篇-字符驱动入门(完美解决cat echo 字符设备乱码的问题)(一))