驱动篇-字符驱动入门(完美解决cat echo 字符设备乱码的问题)(一)
2017-03-02 11:02
344 查看
闲来无事,整理一下驱动入门知识!
大部分与网上整理的差不多,我主要想说的有两个特别的地方,刚入门的人看别人整理的肯定都不知道怎么测试。或者测试结果不像他们所写的那样!
第一点就是用mknod创建的设备名,设备号不能随便写,必须你所写的源文件命名的一致。
比如你在c文件中定义
那么设备名就是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的代码如下,
–>下面是Makefile文件
root@lll-virtual-machine:/home/lll/share/linuxdev/chardev# cat Makefile
上面两个文件都有了之后。直接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来操作这个设备。
–> 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 字符设备乱码的问题的代码
补充资料:
二.主设备号
字符设备通过字符设备文件来存取。字符设备文件由使用ls -l 的输出的第一列的”C”标识;
如果使用ls –l 命令,会看到在设备文件项中有2个数(由一个逗号分隔)这些数字就是设备文件的主次设备编
1>.主设备号用来标识与设备文件相连的驱动程序。
次设备号被驱动程序用来辨别操作的是哪个设备。
主设备号用来反映设备类型*
次设备号用来区分同类型的哪一个设备
内核中如何描述设备号?
dev_t 其实质为unsigned int 32位整数,其中高12位为主设备号,低20位为次设备号。
安装驱动后,从/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);
…
};
大部分与网上整理的差不多,我主要想说的有两个特别的地方,刚入门的人看别人整理的肯定都不知道怎么测试。或者测试结果不像他们所写的那样!
第一点就是用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 4000 =$(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);
…
};
结构中每个成员都是一个函数指针,指 9279 向不同功能的函数,大部分驱动都没有提供所有的函数。对于字符设备来说,常用的成员及解释如下: **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。错误地卸载设备驱动可能会带来严重后果,因此在卸载驱动的时候应该对函数返回值进行判断。
相关文章推荐
- 完美解决关于php gd生成中文字符乱码的问题
- [linux设备驱动程序]scull字符设备驱动编译在新内核编译问题解决方案
- Linux字符设备驱动-globalmem驱动编译加载遇到的问题及解决办法
- 解决截取字符乱码的问题
- 解决XMLHTTP中中文字符传递乱码的问题
- 完美解决PHP中文乱码问题
- VC轻松解析XML文件--CMarkup使用方法(解决解析中文字符出现乱码问题)
- smarty模板截取字符串乱码问题完美解决```````
- 完美解决mysql下utf-8的乱码问题
- 把汉字编码为4位字符,用于解决乱码问题(已减少到3位字符)
- 解决JSP引入JS文件后,JS文件中字符在页面上显示为乱码问题
- VC轻松解析XML文件--CMarkup使用方法(解决解析中文字符出现乱码问题)
- 详细探讨字节码和字符码已经如果解决乱码问题和中文显示问题
- MySQL字符集中文乱码终极解决方案和mysql查询中文问题解决方法[转贴]
- php 中文字符入库或显示乱码问题的解决方法
- 完美解决PHP中文乱码问题
- smarty模板截取字符串乱码问题完美解决```````
- 完美解决JS中汉字显示乱码问题(已解决)
- MySQL字符集中文乱码终极解决方案和mysql查询中文问题解决方法[转贴]
- MySQL字符集中文乱码终极解决方案和mysql查询中文问题解决方法[转贴]