驅動篇-字符驅動入門(解決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源碼

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