驱动篇-字符驱动入门(解决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
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);
    	
   	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。错误地卸载设备驱动可能会带来严重后果,因此在卸载驱动的时候应该对函数返回值进行判断。

github源码

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章