您的位置:首页 > 编程语言 > C语言/C++

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

2017-03-02 11:02 344 查看
闲来无事,整理一下驱动入门知识!

大部分与网上整理的差不多,我主要想说的有两个特别的地方,刚入门的人看别人整理的肯定都不知道怎么测试。或者测试结果不像他们所写的那样!

第一点就是用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。错误地卸载设备驱动可能会带来严重后果,因此在卸载驱动的时候应该对函数返回值进行判断。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c语言 字符驱动