日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux字符驱动开发学习总结

發布時間:2025/4/14 linux 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux字符驱动开发学习总结 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

?linux驅動編寫(虛擬字符設備編寫)



? ? 昨天我們說了一些簡單模塊編寫方法,但是終歸沒有涉及到設備的編寫內容,今天我們就可以了解一下相關方面的內容,并且用一個實例來說明在linux上面設備是如何編寫的。
因為我們是在pc linux上學習驅動的,因此暫時沒有真實的外接設備可以使用,但是這絲毫不影響我們學習的熱情。通過定時器、進程,我們可以仿真出真實設備的各種需求,所以對于系統來說,它是無所謂真設備、假設備的,基本的處理流程對它來說都是一樣的。只要大家一步一步做下去,肯定可以了解linux驅動設備的開發工程的。


? ? 下面,為了說明問題,我們可以編寫一段簡單的char設備驅動代碼,文件名為char.c,


#include <linux/module.h> ?
#include <linux/kernel.h> ?
#include <linux/fs.h> ?
#include <linux/cdev.h> ?
??
static struct cdev chr_dev; ?
static dev_t ndev; ?
??
static int chr_open(struct inode* nd, struct file* filp) ?
{ ?
? ? int major ; ?
? ? int minor; ?
? ? ??
? ? major = MAJOR(nd->i_rdev); ?
? ? minor = MINOR(nd->i_rdev); ?
? ? ??
? ? printk("chr_open, major = %d, minor = %d\n", major, minor); ?
? ? return 0; ?
} ?
??
static ssize_t chr_read(struct file* filp, char __user* u, size_t sz, loff_t* off) ?
{ ?
? ? printk("chr_read process!\n"); ?
? ? return 0; ?
} ?
??
struct file_operations chr_ops = { ?
? ? .owner = THIS_MODULE, ?
? ? .open = chr_open, ?
? ? .read = chr_read ?
}; ?
??
static int demo_init(void) ?
{ ?
? ? int ret; ?
? ? ??
? ? cdev_init(&chr_dev, &chr_ops); ?
? ? ret = alloc_chrdev_region(&ndev, 0, 1, "chr_dev"); ?
? ? if(ret < 0 ) ?
? ? { ?
? ? ? ? return ret; ?
? ? } ?
? ? ??
? ? printk("demo_init(): major = %d, minor = %d\n", MAJOR(ndev), MINOR(ndev)); ?
? ? ret = cdev_add(&chr_dev, ndev, 1); ?
? ? if(ret < 0) ?
? ? { ?
? ? ? ? return ret; ?
? ? } ?
? ? ??
? ? return 0; ?
} ?
??
static void demo_exit(void) ?
{ ?
? ? printk("demo_exit process!\n"); ?
? ? cdev_del(&chr_dev); ?
? ? unregister_chrdev_region(ndev, 1); ?
} ?
??
module_init(demo_init); ?
module_exit(demo_exit); ?
??
MODULE_LICENSE("GPL"); ?
MODULE_AUTHOR("feixiaoxing@163.com"); ?
MODULE_DESCRIPTION("A simple device example!"); ?
? ? 在module_init中的函數是模塊加載時處理的函數,而模塊卸載的函數則是在module_exit中。每一個設備都要對應一個基本的設備數據,當然為了使得這個設備注冊在整個系統當中,我們還需要分配一個設備節點,alloc_chrdev_region就完成這樣一個功能。等到cdev_add的時候,整個設備注冊的過程就全部完成了,就是這么簡單。當然為了編寫這個文件,我們還需要編寫一個Makefile文件,
[cpp] view plain copy
ifneq ($(KERNELRELEASE),) ?
obj-m := char.o ?
??
else ?
PWD ?:= $(shell pwd) ?
KVER := $(shell uname -r) ?
KDIR := /lib/modules/$(KVER)/build ?
all: ?
? ? $(MAKE) -C $(KDIR) M=$(PWD) modules ?
clean: ?
? ? rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions modules.* ?Module.* ?
endif ?
? ? 這個Makefile文件和我們之前編寫的makefile基本上沒有區別,唯一的區別就是文件名稱改成了char.o,僅此而已。為了編寫模塊,我們直接輸入make即可。這時候,char.ko文件就可以生成了。然后,模塊需要被注冊在系統當中,insmod char.ko是少不了的。如果此時,我們還不確信是否模塊已經加入到系統當中,完全可以通過輸入lsmod | grep char進行查找和驗證。為了創建設備節點,我們需要知道設備為我們創建的major、minor數值是多少,所以dmesg | tail 查找一下數值。在我hp的機器上,這兩個數值分別是249和0,所以下面可以利用它們直接創建設備節點了,輸入mknod /dev/chr_dev c 249 0即可,此時可以輸入ls /dev/chr_dev驗證一下。那么,按照這種方法,真的可以訪問這個虛擬設備了嗎,我們可以編寫一段簡單的代碼驗證一下,


#include <stdio.h> ?
#include <fcntl.h> ?
#include <unistd.h> ?
??
#define CHAR_DEV_NAME "/dev/chr_dev" ?
??
int main() ?
{ ?
? ? int ret; ?
? ? int fd; ?
? ? char buf[32]; ?
??
? ? fd = open(CHAR_DEV_NAME, O_RDONLY | O_NDELAY); ?
? ? if(fd < 0) ?
? ? { ?
? ? ? ? printf("open failed!\n"); ?
? ? ? ? return -1; ?
? ? } ?
? ? ??
? ? read(fd, buf, 32); ?
? ? close(fd); ?
? ? ??
? ? return 0; ?
} ?
? ? 代碼的內容非常簡單,就是利用CHAR_DEV_NAME直接打開設備,讀寫設備。當然。首先還是需要對這個文件進行編譯,文件名為test.c,輸入gcc test.c -o test,其次就是運行這個文件,直接輸入./test即可。如果沒有問題的話,那么說明我們的代碼是ok的,但是我們還是沒有看到任何內容。沒關系,我們還是通過dmesg這個命令查看內核中是否存在相關的打印內容,直接輸入dmesg | tail即可。此時如果沒有意外的話,我們就可以看到之前在chr_open和chr_read中留下的printk打印,這說明我們的代碼完全是ok的。


? ? 上面的代碼只是一段小例子,真實的內容要比這復雜一下。不過既然我們都已經入門了,那么后面的內容其實也沒有什么好怕的了。最后有兩個事情補充一下:(1)如果大家在創建節點后想刪除設備節點,直接rm -rf /dev/chr_dev即可;(2)上面這段代碼的原型來自于《深入linux設備驅動程序內核機制》這本書,稍作修改,如果大家對內核機制的內容感興趣,可以參考這本書的內容。


========

?linux驅動編寫(字符設備編寫框架)



? ? 上次我們編寫了一個簡單的字符設備,但是涉及的內容比較少,只有open和read兩個函數。今天,我們打算在此基礎上擴充一下內容。基本的思路是這樣的:(1)編寫字符設備下需要處理的各個函數,包括open、release、read、write、ioctl、lseek函數;(2)編寫一個用戶側的程序來驗證我們編寫的驅動函數是否正確。當然,我們編寫的代碼部分參考了宋寶華先生的《linux設備驅動開發詳解》一書,在此說明一下。


? ? 在開始今天的內容之前,其實有一些題外話可以和大家分享一下。自從工作以來,我個人一直都有一個觀點。那就怎么樣利用簡單的代碼來說明開發中的問題,或者是解釋軟件中的原理,這是一個很高的學問。有些道理看上去云里霧里說不清楚,其實都可以通過編寫代碼來驗證的。os可以、cpu可以、cache可以、編譯器可以、網絡協議也可以,很多很多的內容完全可以通過幾行代碼就可以表達得非常清楚,但是事實上我們并沒有這么做。我想原因無非是這么幾條,一來授業者對相關知識的學習也是停留在概念上而已,二來我們的學習過于死板和教條、太關注知識、不求實踐,三就是學習者自身缺少思考的能力、缺少自我反省的能力、對很多東西不求甚解。對于簡單的linux設備,我們完全可以通過這幾行代碼說清楚問題,免得大家還要苦苦追尋,百思而不得入門。


? ? 好了,說了這么多,我們看看現在的驅動代碼是怎么修改的把。


#include <linux/module.h> ?
#include <linux/types.h> ?
#include <linux/fs.h> ?
#include <linux/errno.h> ?
#include <linux/mm.h> ?
#include <linux/sched.h> ?
#include <linux/init.h> ?
#include <linux/cdev.h> ?
??
#include <asm/io.h> ?
#include <asm/system.h> ?
#include <asm/uaccess.h> ?
??
#define CHRMEM_SIZE 0x1000 ?
#define MEM_CLEAR ? 0x1 ?
??
static int chr_major; ?
??
struct chr_dev ?
{ ?
? ? struct cdev cdev; ?
? ? unsigned char mem[CHRMEM_SIZE]; ?
}; ?
??
struct chr_dev* char_devp; ?
??
int chr_open(struct inode* inode, struct file* filp) ?
{ ?
? ? filp->private_data = char_devp; ?
? ? return 0; ?
} ?
??
int chr_release(struct inode* inode, struct file* filp) ?
{ ?
? ? return ?0; ?
} ?
??
static int chr_ioctl(struct inode* inode, struct file* filp, unsigned int cmd, unsigned long arg) ?
{ ?
? ? struct chr_dev* dev = filp->private_data; ?
? ? ??
? ? switch(cmd) ?
? ? { ?
? ? ? ? case MEM_CLEAR: ?
? ? ? ? ? ? memset(dev->mem, 0, CHRMEM_SIZE); ?
? ? ? ? ? ? break; ?
? ? ? ? ? ? ??
? ? ? ? default: ?
? ? ? ? ? ? return -EINVAL; ?
? ? } ?
? ? ??
? ? return 0; ?
} ?
??
static ssize_t chr_read(struct file* filp, char __user* buf, size_t size, loff_t* ppos) ?
{ ?
? ? unsigned long p = *ppos; ?
? ? unsigned int count = size; ?
? ? int ret = 0; ?
? ? struct chr_dev* dev = filp->private_data; ?
? ? ??
? ? if(p >= CHRMEM_SIZE) ?
? ? { ?
? ? ? ? return 0; ?
? ? } ?
? ? ??
? ? if(count > CHRMEM_SIZE - p) ?
? ? { ?
? ? ? ? return 0; ?
? ? } ?
? ? ??
? ? if(copy_to_user(buf, (void*)(dev->mem + p), count)) ?
? ? { ?
? ? ? ? return -EINVAL; ?
? ? } ?
? ? else ?
? ? { ?
? ? ? ? *ppos += count; ?
? ? ? ? ret = count; ?
? ? } ?
? ? ??
? ? return ret; ?
} ?
??
static ssize_t chr_write(struct file* filp, const char __user* buf, ssize_t size, loff_t *ppos) ?
{ ?
? ? unsigned long p = *ppos; ?
? ? unsigned int count = size; ?
? ? int ret = 0; ?
? ? struct chr_dev* dev = filp->private_data; ?
? ? ??
? ? if(p >= CHRMEM_SIZE) ?
? ? { ?
? ? ? ? return 0; ?
? ? } ?
? ? ??
? ? if(count > CHRMEM_SIZE - p) ?
? ? { ?
? ? ? ? count = CHRMEM_SIZE - p; ?
? ? } ?
? ? ??
? ? if(copy_from_user(dev->mem + p, buf, count)) ?
? ? { ?
? ? ? ? ret = -EINVAL; ?
? ? } ?
? ? else ?
? ? { ?
? ? ? ? *ppos += count; ?
? ? ? ? ret = count; ?
? ? } ?
? ? ??
? ? return ret; ?
} ?
??
static loff_t chr_llseek(struct file* filp, loff_t offset, int orig) ?
{ ?
? ? loff_t ret = 0; ?
? ? ??
? ? /* orig can be SEEK_SET, SEEK_CUR, SEEK_END */ ?
? ? switch(orig) ?
? ? { ?
? ? ? ? case 0: ?
? ? ? ? ? ? if(offset < 0) ?
? ? ? ? ? ? { ?
? ? ? ? ? ? ? ? ret = -EINVAL; ?
? ? ? ? ? ? ? ? break; ?
? ? ? ? ? ? } ?
? ? ? ? ? ? ??
? ? ? ? ? ? if((unsigned int) offset > CHRMEM_SIZE) ?
? ? ? ? ? ? { ?
? ? ? ? ? ? ? ? ret = -EINVAL; ?
? ? ? ? ? ? ? ? break; ?
? ? ? ? ? ? } ?
? ? ? ? ? ? ??
? ? ? ? ? ? filp->f_pos = (unsigned int) offset; ?
? ? ? ? ? ? ret = filp->f_pos; ?
? ? ? ? ? ? break; ?
? ? ? ? ? ? ??
? ? ? ? case 1: ?
? ? ? ? ? ? if((filp->f_pos + offset) > CHRMEM_SIZE) ?
? ? ? ? ? ? { ?
? ? ? ? ? ? ? ? ret = -EINVAL; ?
? ? ? ? ? ? ? ? break; ?
? ? ? ? ? ? } ?
? ? ? ? ? ? ??
? ? ? ? ? ? if((filp->f_pos + offset) < 0) ?
? ? ? ? ? ? { ?
? ? ? ? ? ? ? ? ret = -EINVAL; ?
? ? ? ? ? ? ? ? break; ?
? ? ? ? ? ? } ?
? ? ? ? ? ? ??
? ? ? ? ? ? filp->f_pos += offset; ?
? ? ? ? ? ? ret = filp->f_pos; ?
? ? ? ? ? ? break; ?
? ? ? ? ? ? ??
? ? ? ? default: ?
? ? ? ? ? ? ret = - EINVAL; ?
? ? ? ? ? ? break; ?
? ? } ?
? ? ??
? ? return ret; ?
} ?
??
static const struct file_operations chr_ops = ??
{ ?
? ? .owner ? ?= THIS_MODULE, ?
? ? .llseek ? = chr_llseek, ?
? ? .read ? ? = chr_read, ?
? ? .write ? ?= chr_write, ?
? ? .ioctl ? ?= chr_ioctl, ?
? ? .open ? ? = chr_open, ?
? ? .release ?= chr_release ?
}; ?
??
static void chr_setup_cdev(struct chr_dev* dev, int index) ?
{ ?
? ? int err; ?
? ? int devno = MKDEV(chr_major, index); ?
? ? ??
? ? cdev_init(&dev->cdev, &chr_ops); ?
? ? dev->cdev.owner = THIS_MODULE; ?
? ? ??
? ? err = cdev_add(&dev->cdev, devno, 1); ?
? ? if(err) ?
? ? { ?
? ? ? ? printk(KERN_NOTICE "Error happend!\n"); ?
? ? } ?
} ?
??
int chr_init(void) ?
{ ?
? ? int result; ?
? ? dev_t ndev; ?
??
? ? result = alloc_chrdev_region(&ndev, 0, 1, "chr_dev"); ? ?
? ? if(result < 0 ) ? ?
? ? { ? ?
? ? ? ? return result; ? ?
? ? } ? ??
? ? ??
? ? printk("chr_init(): major = %d, minor = %d\n", MAJOR(ndev), MINOR(ndev)); ?
? ? chr_major = MAJOR(ndev); ?
? ? ??
? ? char_devp = kmalloc(sizeof(struct chr_dev), GFP_KERNEL); ?
? ? if(!char_devp) ?
? ? { ?
? ? ? ? result = -ENOMEM; ?
? ? ? ? goto final; ?
? ? } ?
? ? ??
? ? memset(char_devp, 0, sizeof(struct chr_dev)); ?
? ? chr_setup_cdev(char_devp, 0); ?
? ? return 0; ?
? ? ??
final: ?
? ? unregister_chrdev_region(ndev, 1); ?
? ? return 0; ?
} ?
??
void chr_exit() ?
{ ?
? ? cdev_del(&char_devp->cdev); ?
? ? kfree(char_devp); ?
? ? unregister_chrdev_region(MKDEV(chr_major, 0), 1); ?
} ?
??
module_init(chr_init); ?
module_exit(chr_exit); ?
??
MODULE_LICENSE("GPL"); ?
MODULE_AUTHOR("feixiaoxing!163.com"); ?
MODULE_DESCRIPTION("A simple device example!"); ?
? ? 不可否認,我們的代碼出現了更多的內容,但是基本框架還是一致的。要是說區別,無非就是我們在原來的基礎上添加了新的處理函數而已。說起來,我們對于設備的主要操作也就是這么幾種,大家如果對此的概念已經非常成熟了,那么后面的學習就會輕松很多。當然和之前的驅動一樣,我們也需要make & ?insmod char.ko & mknod /dev/chr_dev c 249 0。接下來,為了驗證上述的內容是否正確,編寫一段簡單的測試代碼是必不可少的。


#include <stdio.h> ?
#include <fcntl.h> ?
#include <unistd.h> ?
??
#define MEM_CLEAR 0x01 ?
#define CHAR_DEV_NAME "/dev/chr_dev" ?
??
int main() ?
{ ?
? ? ? ? int ret; ?
? ? ? ? int fd; ?
? ? ? ? int index; ?
? ? ? ? char buf[32]; ?
??
? ? ? ? /* open device */ ?
? ? ? ? fd = open(CHAR_DEV_NAME, O_RDWR | O_NONBLOCK); ?
? ? ? ? if(fd < 0) ?
? ? ? ? { ?
? ? ? ? ? ? ? ? printf("open failed!\n"); ?
? ? ? ? ? ? ? ? return -1; ?
? ? ? ? } ?
??
? ? ? ? /* set buffer data, which will be stored into device */ ?
? ? ? ? for(index = 0; index < 32; index ++) ?
? ? ? ? { ?
? ? ? ? ? ? ? ? buf[index] = index; ?
? ? ? ? } ?
??
? ? ? ? /* write data */ ?
? ? ? ? write(fd, buf, 32); ?
? ? ? ? memset(buf, 0, 32); ?
??
? ? ? ? /* read data */ ?
? ? ? ? lseek(fd, 0, SEEK_SET); ?
? ? ? ? read(fd, buf, 32); ?
? ? ? ? for(index = 0; index < 32; index ++) ?
? ? ? ? { ?
? ? ? ? ? ? ? ? printf("data[%d] = %d\n", index, buf[index]); ?
? ? ? ? } ?
??
? ? ? ? /* reset all data to zero, read it and check whether it is ok */ ?
? ? ? ? ioctl(fd, MEM_CLEAR, NULL); ?
? ? ? ? lseek(fd, 0, SEEK_SET); ?
? ? ? ? read(fd, buf, 32); ?
? ? ? ? for(index = 0; index < 32; index ++) ?
? ? ? ? { ?
? ? ? ? ? ? ? ? printf("data[%d] = %d\n", index, buf[index]); ?
? ? ? ? } ?
??
? ? ? ? close(fd); ?
? ? ? ? return 0; ?
} ?
? ? 細心的朋友可能發現了,我們在用戶側代碼中使用了很多的處理函數,基本上從open、release、read、write、lseek、ioctl全部包括了。測試代碼處理的流程也非常簡單,首先打開設備,接著寫數據,后面就是讀取數據,最后利用ioctl清除數據,程序返回。因為代碼中包含了注釋的內容,在此我們就不過多贅述了。大家慢慢看代碼,應該都會了解和明白的。注意,用戶測的代碼也要在sudo模式下執行。
========

?Linux字符設備驅動入門


先亮一下裝備:
平臺:VMware 7.0 + Linux ubuntu 3.0.0-12-generic
編譯器:gcc
參考資料:LDD 3
功能:實現簡單的字符操作(從用戶空間向內核空間寫入一串字符;從內核空間讀一個字符到內核空間)
? ? ? ? 眾所周知,字符設備是linux下最基本,也是最常用到的設備,它是學習Linux驅動入門最好的選擇,計算機的東西很多都是相通的,掌握了其中一塊,其他就可以觸類旁通了。在寫驅動前,必須先搞清楚字符設備的框架大概是怎樣的,弄清楚了流程,才開始動手,不要一開始就動手寫代碼!
? ? ? ? 這里所說的框架是參考LLD3上介紹的,內核是基于Linux 2.6,3.0以上的有些地方會不一樣(主要是file_operations中的ioctl修改了),但基本上適用,因為我就是在3.0的內核上實現的!字符設備驅動的初始化流程大概如下所示:
定義相關的設備文件結構體(如file_operation()中的相關成員函數的定義)->向內核申請主設備號(建議采用動態方式) ->申請成功后,調用MAJOR()獲取主設備號 ->初始化cdev的結構體,調用cdev_init() ->調用cdev_add(),注冊cdev到kernel ->注冊設備模塊:module_init()、module_exit()。
======================================================================================================
編寫代碼
====================================================================================================== ?
? ? ? ? ? ? 首先定義兩個全局變量(主設備號和字符設備hellow): ??
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?static int hello_major = 0; ? ? ? ?/* major device number */


? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?static struct cdev hellow; ? ?/* hello device structure */
? ? ? ? ? ? ?然后來看看file_operations(),它的定義可以在../include/linux/fs.h下找到,這里只用到了其中的幾個成員函數:
/* file operations for hello device */
static struct file_operations hello_ops = {
? ? .owner = THIS_MODULE, ?/*owner為所有者字段,防止在使用時模塊被卸載。一邊都設為THIS_MODULE*/
? ? .open = hello_open,
? ? .read = hello_read,
? ? .write = hello_write,
? ? .release = hello_release,
};


? ? ? ?不同于windows驅動程序,Linux設備驅動程序在與硬件設備之間建立了標準的抽象接口。通過這個接口,用戶可以像處理普通文件一樣,通過open,close,read,write等系統調用對設備進行操作,如此一來也大大簡化了linux驅動程序的開發。通過file_operations這個結構體(實際上是一個函數指針的集合),把驅動的操作和設備號聯系起來,程序員所要做的工作只是通過file_operations掛接自己的系統調用函數。 ? ?
??
? ? ? ? 接下來就是實現open,close,read,write操作了,這個驅動什么都沒干,所以很好理解,用戶請求read系統調用時,這個虛擬設備反回相應長度的“A”字符串,用戶write時,將內容顯示到日志中。這里要注意的是,內核空間中不能使用用戶態的malloc,而是使用kmalloc/kfree。而且,用戶read/write提供的buf地址也是用戶態的,內核自然不能直接訪問,需要通過copy_to_user/copy_from_user 進行數據拷貝,具體如下:
/* Open the device */
static int hello_open( struct inode *inode, struct file *filp ){
? ? printk( KERN_NOTICE"Hello device open!\n" );
? ? return 0;
}


/* Close hello_device */
static int hello_release( struct inode *inode, struct file *filp ){
? ? printk( KERN_NOTICE"Hello device close!\n" );
? ? return 0;
}


/* user read from hello device*/
ssize_t hello_read( struct file *flip, char __user *buf, size_t count,loff_t
? ? ? ? ? ? ? ? ? ? *f_pos){
? ? ssize_t retval = 0;
? ? char *bank;
? ? bank = kmalloc(count+1, GFP_KERNEL );
? ? if( bank == NULL )
? ? ? ? return -1;
? ? memset( bank, 'A',count );
? ? if( copy_to_user( buf, bank, count ) ){
? ? ? ? retval = -EFAULT;
? ? ? ? goto out;
? ? }
? ? retval += count;
? ? *(bank+count)=0;
? ? printk( KERN_NOTICE"hello: user read %d bytes from me. %s\n",count,bank );
? out:
? ? kfree(bank);
? ? return retval;
}


/* write to hello device */
ssize_t hello_write( struct file *filp, const char __user *buf, size_t count,
? ? ? ? ? ? ? ? ? ? ?loff_t *f_pos ){
? ? ssize_t retval = 0;
? ? char *bank = kmalloc( count ,GFP_KERNEL );
? ? if( bank == NULL )
? ? ? ? return retval;
? ? if( copy_from_user(bank, buf, count ) ){
? ? ? ? retval = -EFAULT;
? ? ? ? printk( KERN_NOTICE"hello: write error\n" );
? ? ? ? goto out;
? ? }
? ? retval += count;
? ? printk( KERN_NOTICE"hello: user has written %d bytes to me: %s\n",count,
? ? ? ? ? ? bank );
? out:
? ? kfree(bank );
? ? return retval;
}
? ? ? ?你可能會注意到open和release函數頭中的file和inode結構體,inode是內核內部文件的表示,當其指向一個字符設備時,其中的i_cdev成員既包含了指向cdev結構的指針。而file表示打開的文件描述符,對一個文件,若打開多次,則會有多個file結構,但只有一個inode與之對應。
? ? ? ? 因為驅動工作在內核空間,不能使用用戶空間的libc函數,所以程序中打印語句為內核提供的printk,而非printf,KERN_NOTICE宏其實標記的是日志級別(共有八個)不同級別的消息會記錄到不同的地方。如果你運行本模塊,可能會發現printk語句并沒有輸出到控制臺,這是正常的,控制臺只顯示一定級別的消息。當日志級別小于console_loglevel時,消息才能顯示出來。你可以通過dmsg命令看到這些信息,也可以通過修改日志級別使之輸出到你的虛擬終端。
? ? ? ? 作好以上準備工作后,接下來就可以開始進行向內核申請主設備號了。設備號是干什么吃的?據LDD記載,對字符設備的訪問是通過文件系統內的設備名稱進行的。那些被稱為特殊文件、設備文件的節點,通常位于/dev目錄,如果ls -l 查看該目錄,第一列中帶有c標志的即為字符設備,有b標志的為塊設備。而第5、6列所示的兩個數字分別為設備的主、次設備號。通常,主設備號標識設備所用的驅動程序(現在大多設備仍然采用“一個主設備號對應一個驅動程序”的規則),次設備號用于確定設備,比如你有兩塊網卡,使用同一驅動,主設備號相同,那么他們將由次設備號區分。
/* Module housekeeping */
static int hello_init(void){
? ? int result;
? ? dev_t dev = MKDEV( hello_major, 0 );/*to transfer major as dev_t type*/


? ? /* alloc the major ? ?device number dynamicly */
? ? result = alloc_chrdev_region(&dev, 0 ,1, "hello" );
? ? if( result < 0 ){
? ? ? ? printk( KERN_NOTICE"Hello: unable to get major %d\n",hello_major );
? ? ? ? return result;
? ? }
? ? hello_major = MAJOR(dev);
? ? /* set up devices, in this case, there is only one device */
? ? printk( KERN_NOTICE"hello init. major:%d, minor:%d\n",hello_major,0 );
? ? //printk( KERN_ALERT"hello init: %d, %d\n",hello_major,0 );
? ? hello_setup_cdev(&hellow, 0 , &hello_ops );
? ??
? ? return 0;
? ??
}


/* Exit routine */
static void hello_exit(void){
? ? /* remove the cdev from kernel */
? ? cdev_del(&hellow );
? ? /* release the device numble alloced earlier */
? ? unregister_chrdev_region( MKDEV( hello_major, 0 ), 1 );
? ? printk( KERN_NOTICE"hello exit. major:%d,minor %d\n",hello_major,0 );
}
? ? ? ? 這里主設備號的分配由alloc_chrdev_region(第一個參數為dev_t 指針,用來存放設備編號,第二個參數為要使用的第一個次設備號,通常為0,第三個參數為請求的連續設備編號個數)動態分配,當然也可以靜態指定一個未被使用的主設備號,相應函數為register_chrdev_region,但不推薦這樣做。在模塊被卸載時(hello_exit),通過unregister_chrdev_region釋放設備號。MKDEV宏將給出的主、次設備號轉換成dev_t類型,MAJOR,MINOR分別從dev_t中析取主次設備號。
這里幾個函數的原型為:
int register_chrdev_region(dev_t first, unsigned int count, char *name);
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
void unregister_chrdev_region(dev_t first, unsigned int count);


? ? ? ? ?然后進入hello_setup_cdev函數,對設備進行初始化這里cdev結構體是內核內部使用來表示字符設備的。在內核調用設備操作之前,必須分配并注冊一個或多個這樣的結構。為了方便,沒有動態使用cdev_alloc函數分配空間,而是定義了一個全局靜態cdev變量。通常你可以將你的cdev嵌入到自定義的結構體中(這個驅動很naive,沒有這么做),通過cdev_init 函數初始化。最后調用cdev_add(),注冊cdev到內核。
? ? ? ? ?/* set up the cdev stucture for a device */
static void hello_setup_cdev( struct cdev *dev, int minor, struct
file_operations *fops ){
? ? int err;
? ? int devno = MKDEV( hello_major, minor );
? ? /* initialize the cdev struct */
? ? cdev_init( dev,fops );
? ? dev->owner = THIS_MODULE;
? ? err = cdev_add( dev, devno, 1 ); /* register the cdev in the kernel */
? ? if( err )
? ? ? ? printk( KERN_NOTICE"Error %d adding hello%d\n",err ,minor );
}


? ? ? ? 最后module_init( hello_init ); module_exit( hello_exit );指定了模塊初始化和關閉函數。MODULE_LICENSE( "Dual BSD/GPL" ); ?指定模塊使用的許可證能被內核識別的許可證有GPL、GPL v2、 Dual BSD/GPL、 Dual MPL/GPL、Proprietary(專有)等,如果模塊沒有顯式標記許可證,則會被認定為“專有”,內核加載這樣的模塊會被“污染”。


/* register the init and exit routine of the module */
module_init( hello_init );
module_exit( hello_exit );
MODULE_AUTHOR( "jabenwang" );
MODULE_LICENSE( "Dual BSD/GPL" );
? ? ? ? ? ? ?到這里,這個字符設備驅動已經完成,接下來就是編譯它。
======================================================================================================
編譯代碼
======================================================================================================
? ? ? ? 這個是我寫的makefile文件,在我臺機上我沒把這個模塊加入到內核源碼的字符設備目錄下,而是放在了用戶目錄下面。但這個makefile文件對以上兩種情況都支持:
#wjb add 2011-10-21


ifneq ($(KERNELRELEASE), )
obj-m := hellow.o


else


KERNELDIR =/lib/modules/$(shell uname -r)/build
PWD :=$(shell pwd)


.PHONY: modules modules_install clean


modules:
? ? $(MAKE) -C $(KERNELDIR) M=$(PWD) modules


modules_install:
? ? $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install


clean:
? ? $(MAKE) -C $(KERNELDIR) M=$(PWD) clean
endif
======================================================================================================
模塊加載&設備文件節點構造:
======================================================================================================
? ? ? ? ? ? ??
1. 編譯成功后,會得到hellow.ko, 這時你就可以通過insmod命令加載模塊
# insmod hellow.ko
這時你的日志控制臺中會出現hello_init中的打印信息,如果你使用lsmod列出當前已加載模塊,會發現hellow模塊赫然在目:
root@ubuntu:~/share/hellow# ls
a.out ? ? ?hellow.ko ? ? hellow.o ?Makefile ? ? ? Module.symvers
hellow.c ? hellow.mod.c ?main.c ? ?Makefile~
hellow.c~ ?hellow.mod.o ?main.c~ ? modules.order
root@ubuntu:~/share/hellow# insmod hellow.ko
root@ubuntu:~/share/hellow# dmesg | tail?
[ 3711.851658] hello init. major:251, minor:0


2.要想使用驅動,你需要在/dev 目錄下建立設備文件節點,語法是
mknod [options]name {bc} major minor
這里需要知道設備的主、次設備號,何以知之?使用cat /proc/devices | grep hello 你就會得到其主設備號
比如我這里得知hellow的主設備號為251
那么就用下面的命令:
root@ubuntu:~/share/hellow# mknod /dev/hellow c 251 0
c表示字符設備,這樣就可以通過該設備文件操作設備了。


======================================================================================================
測試程序:
======================================================================================================
? ?現在就可以通過系統調用操作設備了,我寫了一個測試程序來調用:
#include <stdio.h> ?
#include <fcntl.h> ?
#include <stdlib.h> ?
#include <string.h> ?
#include <sys/types.h> ?
#include <sys/stat.h>
#include <unistd.h>
#include <sys/ioctl.h>




? ? ? int main(void)
{
? ? int fd, ret;
? ? char *buf = " Hello world !";
? ? char temp[10] = "0";
? ? fd = open ( "/dev/hellow" , O_RDWR);
? ? if ( fd == -1 )
? ? {
? ? ? perror("open");
? ? ? exit(0);
? ? ? ? }


? ? ? ? ret = write( fd, buf,strlen(buf));


? ? if ( ret == -1 )
? ? {
? ? ? perror("write");
? ? ? exit(0);
? ? }
? ??
? ? ? ? ret = read ( fd ,temp, strlen(temp) );
? ? if ( ret == -1)
? ? {
? ? ? perror("read");
? ? ? exit(0);
? ? }


? ? close(fd);
? ? return 0;
}
編譯之:
root@ubuntu:~/share/hellow# gcc main.c
生成的目標文件為a.out
運行之:
root@ubuntu:~/share/hellow# ./a.outroot@ubuntu:~/share/hellow# dmesg | tail
結果:
[ 4082.930492] Hello device open!
[ 4082.930520] hello: user has written 14 bytes to me: ?Hello world !
[ 4082.930524] hello: user read 1 bytes from me. A
[ 4082.930829] Hello device close!
? ? ? ? ? 當然,如果你想移除這個字符設備,可以輸入如下命令:
root@ubuntu:~/share/hellow# rmmod hellow
root@ubuntu:~/share/hellow# dmesg | tail?
結果顯示如下信息,說明已經移除:
[ 4344.602407] hello exit. major:251,minor 0


========

linux設備驅動程序之簡單字符設備驅動



一、linux系統將設備分為3類:字符設備、塊設備、網絡設備。使用驅動程序:


1、字符設備:是指只能一個字節一個字節讀寫的設備,不能隨機讀取設備內存中的某一數據,讀取數據需要按照先后數據。字符設備是面向流的設備,常見的字符設備有鼠標、鍵盤、串口、控制臺和LED設備等。
2、塊設備:是指可以從設備的任意位置讀取一定長度數據的設備。塊設備包括硬盤、磁盤、U盤和SD卡等。


  每一個字符設備或塊設備都在/dev目錄下對應一個設備文件。linux用戶程序通過設備文件(或稱設備節點)來使用驅動程序操作字符設備和塊設備。


?


二、字符設備驅動程序基礎:
1、主設備號和次設備號(二者一起為設備號):
  一個字符設備或塊設備都有一個主設備號和一個次設備號。主設備號用來標識與設備文件相連的驅動程序,用來反映設備類型。次設備號被驅動程序用來辨別操作的是哪個設備,用來區分同類型的設備。
  linux內核中,設備號用dev_t來描述,2.6.28中定義如下:
  typedef u_long dev_t;
  在32位機中是4個字節,高12位表示主設備號,低12位表示次設備號。


可以使用下列宏從dev_t中獲得主次設備號:                   也可以使用下列宏通過主次設備號生成dev_t:
MAJOR(dev_t dev);                              MKDEV(int major,int minor);
MINOR(dev_t dev);




//宏定義:
#define MINORBITS ? ?20
#define MINORMASK ? ?((1U << MINORBITS) - 1)
#define MAJOR(dev) ? ?((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ? ?((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) ? ?(((ma) << MINORBITS) | (mi))


?


2、分配設備號(兩種方法):


(1)靜態申請:
int register_chrdev_region(dev_t from, unsigned count, const char *name);


/**
?* register_chrdev_region() - register a range of device numbers
?* @from: the first in the desired range of device numbers; must include
?* ? ? ? ?the major number.
?* @count: the number of consecutive device numbers required
?* @name: the name of the device or driver.
?*
?* Return value is zero on success, a negative error code on failure.
?*/


(2)動態分配:


int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);



int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
? ? ?/**
?* alloc_chrdev_region() - register a range of char device numbers
?* @dev: output parameter for first assigned number
?* @baseminor: first of the requested range of minor numbers
?* @count: the number of minor numbers required
?* @name: the name of the associated device or driver
?*
?* Allocates a range of char device numbers. ?The major number will be
?* chosen dynamically, and returned (along with the first minor number)
?* in @dev. ?Returns zero or a negative error code.
?*/


注銷設備號:


void unregister_chrdev_region(dev_t from, unsigned count);


創建設備文件:
利用cat /proc/devices查看申請到的設備名,設備號。
(1)使用mknod手工創建:mknod filename type major minor
(2)自動創建;


  利用udev(mdev)來實現設備文件的自動創建,首先應保證支持udev(mdev),由busybox配置。在驅動初始化代碼里調用class_create為該設備創建一個class,再為每個設備調用device_create創建對應的設備。



3、字符設備驅動程序重要的數據結構:
(1)struct file:代表一個打開的文件描述符,系統中每一個打開的文件在內核中都有一個關聯的struct file。它由內核在open時創建,并傳遞給在文件上操作的任何函數,直到最后關閉。當文件的所有實例都關閉之后,內核釋放這個數據結構。


//重要成員: ? ??
const struct file_operations ? ?*f_op; ?//該操作是定義文件關聯的操作的。內核在執行open時對這個指針賦值。?
off_t ?f_pos; ? ? //該文件讀寫位置。
void ? ? ? ? ? ?*private_data;//該成員是系統調用時保存狀態信息非常有用的資源。
(2)struct inode:用來記錄文件的物理信息。它和代表打開的file結構是不同的。一個文件可以對應多個file結構,但只有一個inode結構。inode一般作為file_operations結構中函數的參數傳遞過來。
  inode譯成中文就是索引節點。每個存儲設備或存儲設備的分區(存儲設備是硬盤、軟盤、U盤 ... ... )被格式化為文件系統后,應該有兩部份,一部份是inode,另一部份是Block,Block是用來存儲數據用的。而inode呢,就是用來存儲這些數據的信息,這些信息包括文件大小、屬主、歸屬的用戶組、讀寫權限等。inode為每個文件進行信息索引,所以就有了inode的數值。操作系統根據指令,能通過inode值最快的找到相對應的文件。


dev_t i_rdev; ? ?//對表示設備文件的inode結構,該字段包含了真正的設備編號。
struct cdev *i_cdev; ? ? //是表示字符設備的內核的內部結構。當inode指向一個字符設備文件時,該字段包含了指向struct cdev結構的指針。
//我們也可以使用下邊兩個宏從inode中獲得主設備號和此設備號:
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);
(3)struct file_operations

本部分來源于:http://blog.chinaunix.net/space.php?uid=20729583&do=blog&id=1884550


struct file_operations ***_ops={
?.owner = ?THIS_MODULE,
?.llseek = ?***_llseek,
?.read = ?***_read,
?.write = ?***_write,
?.ioctl = ?***_ioctl,
?.open = ?***_open,
?.release = ***_release,?
?。。。 ?。。。
};


struct module *owner;
?/*第一個 file_operations 成員根本不是一個操作; 它是一個指向擁有這個結構的模塊的指針.
?這個成員用來在它的操作還在被使用時阻止模塊被卸載. 幾乎所有時間中, 它被簡單初始化為?
THIS_MODULE, 一個在 <linux/module.h> 中定義的宏.這個宏比較復雜,在進行簡單學習操作的時候,一般初始化為THIS_MODULE。*/


loff_t (*llseek) (struct file * filp , loff_t ?p, ?int ?orig);
/*(指針參數filp為進行讀取信息的目標文件結構體指針;參數 p 為文件定位的目標偏移量;參數orig為對文件定位
的起始地址,這個值可以為文件開頭(SEEK_SET,0,當前位置(SEEK_CUR,1),文件末尾(SEEK_END,2))
llseek 方法用作改變文件中的當前讀/寫位置, 并且新位置作為(正的)返回值.
loff_t 參數是一個"long offset", 并且就算在 32位平臺上也至少 64 位寬. 錯誤由一個負返回值指示.
如果這個函數指針是 NULL, seek 調用會以潛在地無法預知的方式修改 file 結構中的位置計數器( 在"file 結構" 一節中描述).*/

ssize_t (*read) (struct file * filp, char __user * buffer, size_t ? ?size , loff_t * ?p);
/*(指針參數 filp 為進行讀取信息的目標文件,指針參數buffer 為對應放置信息的緩沖區(即用戶空間內存地址),
參數size為要讀取的信息長度,參數 p 為讀的位置相對于文件開頭的偏移,在讀取信息后,這個指針一般都會移動,移動的值為要讀取信息的長度值)
這個函數用來從設備中獲取數據. 在這個位置的一個空指針導致 read 系統調用以 -EINVAL("Invalid argument") 失敗.
?一個非負返回值代表了成功讀取的字節數( 返回值是一個 "signed size" 類型, 常常是目標平臺本地的整數類型).*/

ssize_t (*aio_read)(struct kiocb * ?, char __user * ?buffer, size_t ?size , ?loff_t ? p);
/*可以看出,這個函數的第一、三個參數和本結構體中的read()函數的第一、三個參數是不同 的,
異步讀寫的第三個參數直接傳遞值,而同步讀寫的第三個參數傳遞的是指針,因為AIO從來不需要改變文件的位置。
異步讀寫的第一個參數為指向kiocb結構體的指針,而同步讀寫的第一參數為指向file結構體的指針,每一個I/O請求都對應一個kiocb結構體);
初始化一個異步讀 -- 可能在函數返回前不結束的讀操作.如果這個方法是 NULL, 所有的操作會由 read 代替進行(同步地).
(有關linux異步I/O,可以參考有關的資料,《linux設備驅動開發詳解》中給出了詳細的解答)*/

ssize_t (*write) (struct file * ?filp, const char __user * ? buffer, size_t ?count, loff_t * ppos);
/*(參數filp為目標文件結構體指針,buffer為要寫入文件的信息緩沖區,count為要寫入信息的長度,
ppos為當前的偏移位置,這個值通常是用來判斷寫文件是否越界)
發送數據給設備. 如果 NULL, -EINVAL 返回給調用 write 系統調用的程序. 如果非負, 返回值代表成功寫的字節數.
(注:這個操作和上面的對文件進行讀的操作均為阻塞操作)*/

ssize_t (*aio_write)(struct kiocb *, const char __user * ?buffer, size_t ?count, loff_t * ppos);
/*初始化設備上的一個異步寫.參數類型同aio_read()函數;*/

int (*readdir) (struct file * ?filp, void *, filldir_t);
/*對于設備文件這個成員應當為 NULL; 它用來讀取目錄, 并且僅對文件系統有用.*/

unsigned int (*poll) (struct file *, struct poll_table_struct *);
/*(這是一個設備驅動中的輪詢函數,第一個參數為file結構指針,第二個為輪詢表指針)
這個函數返回設備資源的可獲取狀態,即POLLIN,POLLOUT,POLLPRI,POLLERR,POLLNVAL等宏的位“或”結果。
每個宏都表明設備的一種狀態,如:POLLIN(定義為0x0001)意味著設備可以無阻塞的讀,POLLOUT(定義為0x0004)意味著設備可以無阻塞的寫。
(poll 方法是 3 個系統調用的后端: poll, epoll, 和 select, 都用作查詢對一個或多個文件描述符的讀或寫是否會阻塞.
?poll 方法應當返回一個位掩碼指示是否非阻塞的讀或寫是可能的, 并且, 可能地, 提供給內核信息用來使調用進程睡眠直到 I/O 變為可能.?
如果一個驅動的 poll 方法為 NULL, 設備假定為不阻塞地可讀可寫.
(這里通常將設備看作一個文件進行相關的操作,而輪詢操作的取值直接關系到設備的響應情況,可以是阻塞操作結果,同時也可以是非阻塞操作結果)*/


int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
/*(inode 和 filp 指針是對應應用程序傳遞的文件描述符 fd 的值, 和傳遞給 open 方法的相同參數.
cmd 參數從用戶那里不改變地傳下來, 并且可選的參數 arg 參數以一個 unsigned long 的形式傳遞, 不管它是否由用戶給定為一個整數或一個指針.
如果調用程序不傳遞第 3 個參數, 被驅動操作收到的 arg 值是無定義的.
因為類型檢查在這個額外參數上被關閉, 編譯器不能警告你如果一個無效的參數被傳遞給 ioctl, 并且任何關聯的錯誤將難以查找.)
ioctl 系統調用提供了發出設備特定命令的方法(例如格式化軟盤的一個磁道, 這不是讀也不是寫). 另外, 幾個 ioctl 命令被內核識別而不必引用 fops 表.
?如果設備不提供 ioctl 方法, 對于任何未事先定義的請求(-ENOTTY, "設備無這樣的 ioctl"), 系統調用返回一個錯誤.*/

int (*mmap) (struct file *, struct vm_area_struct *);
/*mmap 用來請求將設備內存映射到進程的地址空間. 如果這個方法是 NULL, mmap 系統調用返回 -ENODEV.
(如果想對這個函數有個徹底的了解,那么請看有關“進程地址空間”介紹的書籍)*/

int (*open) (struct inode * inode , struct file * ?filp ) ;
/*(inode 為文件節點,這個節點只有一個,無論用戶打開多少個文件,都只是對應著一個inode結構;
但是filp就不同,只要打開一個文件,就對應著一個file結構體,file結構體通常用來追蹤文件在運行時的狀態信息)
?盡管這常常是對設備文件進行的第一個操作, 不要求驅動聲明一個對應的方法. 如果這個項是 NULL, 設備打開一直成功, 但是你的驅動不會得到通知.
與open()函數對應的是release()函數。*/

int (*flush) (struct file *);
/*flush 操作在進程關閉它的設備文件描述符的拷貝時調用; 它應當執行(并且等待)設備的任何未完成的操作.
這個必須不要和用戶查詢請求的 fsync 操作混淆了. 當前, flush 在很少驅動中使用;
?SCSI 磁帶驅動使用它, 例如, 為確保所有寫的數據在設備關閉前寫到磁帶上. 如果 flush 為 NULL, 內核簡單地忽略用戶應用程序的請求.*/

int (*release) (struct inode *, struct file *);
/*release ()函數當最后一個打開設備的用戶進程執行close()系統調用的時候,內核將調用驅動程序release()函數:
void release(struct inode inode,struct file *file),release函數的主要任務是清理未結束的輸入輸出操作,釋放資源,用戶自定義排他標志的復位等。
? ? 在文件結構被釋放時引用這個操作. 如同 open, release 可以為 NULL.*/

int(*synch)(struct file *,struct dentry *,int datasync);
//刷新待處理的數據,允許進程把所有的臟緩沖區刷新到磁盤。


int (*aio_fsync)(struct kiocb *, int);
?/*這是 fsync 方法的異步版本.所謂的fsync方法是一個系統調用函數。系統調用fsync
把文件所指定的文件的所有臟緩沖區寫到磁盤中(如果需要,還包括存有索引節點的緩沖區)。
相應的服務例程獲得文件對象的地址,并隨后調用fsync方法。通常這個方法以調用函數__writeback_single_inode()結束,
這個函數把與被選中的索引節點相關的臟頁和索引節點本身都寫回磁盤。*/

int (*fasync) (int, struct file *, int);
//這個函數是系統支持異步通知的設備驅動,下面是這個函數的模板:

static int ***_fasync(int fd,struct file *filp,int mode)
{
? ? struct ***_dev * dev=filp->private_data;
? ? return fasync_helper(fd,filp,mode,&dev->async_queue);//第四個參數為 fasync_struct結構體指針的指針。
//這個函數是用來處理FASYNC標志的函數。(FASYNC:表示兼容BSD的fcntl同步操作)當這個標志改變時,驅動程序中的fasync()函數將得到執行。
}
/*此操作用來通知設備它的 FASYNC 標志的改變. 異步通知是一個高級的主題, 在第 6 章中描述.
這個成員可以是NULL 如果驅動不支持異步通知.*/

int (*lock) (struct file *, int, struct file_lock *);
//lock 方法用來實現文件加鎖; 加鎖對常規文件是必不可少的特性, 但是設備驅動幾乎從不實現它.

ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
/*這些方法實現發散/匯聚讀和寫操作. 應用程序偶爾需要做一個包含多個內存區的單個讀或寫操作;
?這些系統調用允許它們這樣做而不必對數據進行額外拷貝. 如果這些函數指針為 NULL, read 和 write 方法被調用( 可能多于一次 ).*/

ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *);
/*這個方法實現 sendfile 系統調用的讀, 使用最少的拷貝從一個文件描述符搬移數據到另一個.
例如, 它被一個需要發送文件內容到一個網絡連接的 web 服務器使用. 設備驅動常常使 sendfile 為 NULL.*/

ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
/*sendpage 是 sendfile 的另一半; 它由內核調用來發送數據, 一次一頁, 到對應的文件. 設備驅動實際上不實現 sendpage.*/

unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
/*這個方法的目的是在進程的地址空間找一個合適的位置來映射在底層設備上的內存段中.
這個任務通常由內存管理代碼進行; 這個方法存在為了使驅動能強制特殊設備可能有的任何的對齊請求. 大部分驅動可以置這個方法為 NULL.[10]*/

int (*check_flags)(int)
//這個方法允許模塊檢查傳遞給 fnctl(F_SETFL...) 調用的標志.

int (*dir_notify)(struct file *, unsigned long);
//這個方法在應用程序使用 fcntl 來請求目錄改變通知時調用. 只對文件系統有用; 驅動不需要實現 dir_notify.
?
三、字符設備驅動程序設計:

1.設備注冊:
在linux2.6內核中,字符設備使用struct cdev來描述;

struct cdev
{
? struct kobject kobj;//內嵌的kobject對象
? struct module *owner;//所屬模塊
? struct file_operations *ops;//文件操作結構體
? struct list_head list;
? dev_t dev;//設備號,長度為32位,其中高12為主設備號,低20位為此設備號
? unsigned int count;
};

字符設備的注冊分為三個步驟:

(1)分配cdev: struct cdev *cdev_alloc(void);
(2)初始化cdev: void cdev_init(struct cdev *cdev, const struct file_operations *fops);
(3)添加cdev: int cdev_add(struct cdev *p, dev_t dev, unsigned count)


/**
?* cdev_add() - add a char device to the system
?* @p: the cdev structure for the device
?* @dev: the first device number for which this device is responsible
?* @count: the number of consecutive minor numbers corresponding to this
?* ? ? ? ? device
?*
?* cdev_add() adds the device represented by @p to the system, making it
?* live immediately. ?A negative error code is returned on failure.
?*/


2.設備操作的實現:file_operations函數集的實現(要明確某個函數什么時候被調用?調用來做什么操作?)
特別注意:驅動程序應用程序的數據交換:
  驅動程序和應用程序的數據交換是非常重要的。file_operations中的read()和write()函數,就是用來在驅動程序和應用程序間交換數據的。通過數據交換,驅動程序和應用程序可以彼此了解對方的情況。但是驅動程序和應用程序屬于不同的地址空間。驅動程序不能直接訪問應用程序的地址空間;同樣應用程序也不能直接訪問驅動程序的地址空間,否則會破壞彼此空間中的數據,從而造成系統崩潰,或者數據損壞。安全的方法是使用內核提供的專用函數,完成數據在應用程序空間和驅動程序空間的交換。這些函數對用戶程序傳過來的指針進行了嚴格的檢查和必要的轉換,從而保證用戶程序與驅動程序交換數據的安全性。這些函數有:

unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);?
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);?
put_user(local,user);?
get_user(local,user);
?
3.設備注銷:void cdev_del(struct cdev *p);


四、字符設備驅動小結:

  字符設備是3大類設備(字符設備、塊設備、網絡設備)中較簡單的一類設備,其驅動程序中完成的主要工作是初始化、添加和刪除cdev結構體,申請和釋放設備號,以及填充file_operation結構體中操作函數,并實現file_operations結構體中的read()、write()、ioctl()等重要函數。如圖所示為cdev結構體、file_operations和用戶空間調用驅動的關系。
?

五:字符設備驅動程序分析:

(1)memdev.h

#ifndef _MEMDEV_H_
#define _MEMDEV_H_

#ifndef MEMDEV_MAJOR
#define MEMDEV_MAJOR 251 ? /*預設的mem的主設備號*/
#endif

#ifndef MEMDEV_NR_DEVS
#define MEMDEV_NR_DEVS 2 ? ?/*設備數*/
#endif

#ifndef MEMDEV_SIZE
#define MEMDEV_SIZE 4096
#endif

/*mem設備描述結構體*/
struct mem_dev ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
{ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? char *data; ? ? ? ? ? ? ? ? ? ? ?
? unsigned long size; ? ? ??
};

#endif /* _MEMDEV_H_ */

(2)memdev.c

static mem_major = MEMDEV_MAJOR;

module_param(mem_major, int, S_IRUGO);

struct mem_dev *mem_devp; /*設備結構體指針*/

struct cdev cdev;?

/*文件打開函數*/
int mem_open(struct inode *inode, struct file *filp)
{
? ? struct mem_dev *dev;
? ??
? ? /*獲取次設備號*/
? ? int num = MINOR(inode->i_rdev);

? ? if (num >= MEMDEV_NR_DEVS)?
? ? ? ? ? ? return -ENODEV;
? ? dev = &mem_devp[num];
? ??
? ? /*將設備描述結構指針賦值給文件私有數據指針*/
? ? filp->private_data = dev;
? ??
? ? return 0;?
}

/*文件釋放函數*/
int mem_release(struct inode *inode, struct file *filp)
{
? return 0;
}

/*讀函數*/
static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
? unsigned long p = ?*ppos; ? ? ? ?/*記錄文件指針偏移位置*/ ?
? unsigned int count = size; ? ?/*記錄需要讀取的字節數*/?
? int ret = 0; ? ?/*返回值*/ ?
? struct mem_dev *dev = filp->private_data; /*獲得設備結構體指針*/

? /*判斷讀位置是否有效*/
? if (p >= MEMDEV_SIZE) ? ?/*要讀取的偏移大于設備的內存空間*/ ?
? ? return 0;
? if (count > MEMDEV_SIZE - p) ? ? /*要讀取的字節大于設備的內存空間*/?
? ? count = MEMDEV_SIZE - p;

? /*讀數據到用戶空間:內核空間->用戶空間交換數據*/ ?
? if (copy_to_user(buf, (void*)(dev->data + p), count))
? {
? ? ret = ?- EFAULT;
? }
? else
? {
? ? *ppos += count;
? ? ret = count;
? ??
? ? printk(KERN_INFO "read %d bytes(s) from %d\n", count, p);
? }


? return ret;
}

/*寫函數*/
static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
? unsigned long p = ?*ppos;
? unsigned int count = size;
? int ret = 0;
? struct mem_dev *dev = filp->private_data; /*獲得設備結構體指針*/
??
? /*分析和獲取有效的寫長度*/
? if (p >= MEMDEV_SIZE)
? ? return 0;
? if (count > MEMDEV_SIZE - p) ? ?/*要寫入的字節大于設備的內存空間*/
? ? count = MEMDEV_SIZE - p;
? ??
? /*從用戶空間寫入數據*/
? if (copy_from_user(dev->data + p, buf, count))
? ? ret = ?- EFAULT;
? else
? {
? ? *ppos += count; ? ? ?/*增加偏移位置*/ ?
? ? ret = count; ? ? ?/*返回實際的寫入字節數*/?
? ??
? ? printk(KERN_INFO "written %d bytes(s) from %d\n", count, p);
? }


? return ret;
}

/* seek文件定位函數 */
static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
{?
? ? loff_t newpos; ? ? ?


? ? switch(whence) {
? ? ? case 0: /* SEEK_SET */ ? ? ? /*相對文件開始位置偏移*/?
? ? ? ? newpos = offset; ? ? ? ? ? /*更新文件指針位置*/
? ? ? ? break;


? ? ? case 1: /* SEEK_CUR */
? ? ? ? newpos = filp->f_pos + offset; ? ?
? ? ? ? break;


? ? ? case 2: /* SEEK_END */
? ? ? ? newpos = MEMDEV_SIZE -1 + offset;
? ? ? ? break;


? ? ? default: /* can't happen */
? ? ? ? return -EINVAL;
? ? }
? ? if ((newpos<0) || (newpos>MEMDEV_SIZE))
? ? ? ? return -EINVAL;
? ? ? ??
? ? filp->f_pos = newpos;
? ? return newpos;


}

/*文件操作結構體*/
static const struct file_operations mem_fops =
{
? .owner = THIS_MODULE,
? .llseek = mem_llseek,
? .read = mem_read,
? .write = mem_write,
? .open = mem_open,
? .release = mem_release,
};

/*設備驅動模塊加載函數*/
static int memdev_init(void)
{
? int result;
? int i;

? dev_t devno = MKDEV(mem_major, 0);


? ?/* 申請設備號,當xxx_major不為0時,表示靜態指定;當為0時,表示動態申請*/?
? /* 靜態申請設備號*/
? if (mem_major)
? ? result = register_chrdev_region(devno, 2, "memdev");
? else ?/* 動態分配設備號 */
? {
? ? result = alloc_chrdev_region(&devno, 0, 2, "memdev");
? ? mem_major = MAJOR(devno); ? ?/*獲得申請的主設備號*/
? } ?
??
? if (result < 0)
? ? return result;


?/*初始化cdev結構,并傳遞file_operations結構指針*/?
? cdev_init(&cdev, &mem_fops); ? ?
? cdev.owner = THIS_MODULE; ? ?/*指定所屬模塊*/
? cdev.ops = &mem_fops;
??
? /* 注冊字符設備 */
? cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);
? ?
? /* 為設備描述結構分配內存*/
? mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
? if (!mem_devp) ? ?/*申請失敗*/
? {
? ? result = ?- ENOMEM;
? ? goto fail_malloc;
? }
? memset(mem_devp, 0, sizeof(struct mem_dev));
??
? /*為設備分配內存*/
? for (i=0; i < MEMDEV_NR_DEVS; i++)?
? {
? ? ? ? mem_devp[i].size = MEMDEV_SIZE;
? ? ? ? mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);
? ? ? ? memset(mem_devp[i].data, 0, MEMDEV_SIZE);
? }
? ??
? return 0;


? fail_malloc:?
? unregister_chrdev_region(devno, 1);
??
? return result;
}


/*模塊卸載函數*/
static void memdev_exit(void)
{
? cdev_del(&cdev); ? /*注銷設備*/
? kfree(mem_devp); ? ? /*釋放設備結構體內存*/
? unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*釋放設備號*/
}


MODULE_AUTHOR("David Xie");
MODULE_LICENSE("GPL");


module_init(memdev_init);
module_exit(memdev_exit);


(3)應用程序(測試文件):app-mem.c

#include <stdio.h>

int main()
{
? ? FILE *fp0 = NULL;
? ? char Buf[4096];
? ??
? ? /*初始化Buf*/
? ? strcpy(Buf,"Mem is char dev!");
? ? printf("BUF: %s\n",Buf);
? ??
? ? /*打開設備文件*/
? ? fp0 = fopen("/dev/memdev0","r+");
? ? if (fp0 == NULL)
? ? {
? ? ? ? printf("Open Memdev0 Error!\n");
? ? ? ? return -1;
? ? }
? ??
? ? /*寫入設備*/
? ? fwrite(Buf, sizeof(Buf), 1, fp0);
? ??
? ? /*重新定位文件位置(思考沒有該指令,會有何后果)*/
? ? fseek(fp0,0,SEEK_SET);
? ??
? ? /*清除Buf*/
? ? strcpy(Buf,"Buf is NULL!");
? ? printf("BUF: %s\n",Buf);
? ??
? ??
? ? /*讀出設備*/
? ? fread(Buf, sizeof(Buf), 1, fp0);
? ??
? ? /*檢測結果*/
? ? printf("BUF: %s\n",Buf);
? ??
? ? return 0; ? ?


}

測試步驟:


1)cat /proc/devices看看有哪些編號已經被使用,我們選一個沒有使用的XXX。
2)insmod memdev.ko
3)通過"mknod /dev/memdev0 c XXX 0"命令創建"/dev/memdev0"設備節點。
4)交叉編譯app-mem.c文件,下載并執行:
#./app-mem,顯示:
Mem is char dev!
========

Linux 字符驅動開發心得



Linux字符驅動框架相比初學還是比較難記的,在學了一陣子字符驅動的開發后對于框架的搭建總結出了幾個字 。


對于框架來講主要要完成兩步。


申請設備號,注冊字符驅動


其關鍵代碼就兩句



int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);//動態申請設備號
?
int cdev_add(struct cdev *, dev_t, unsigned); ? ? ? ? ? ? ? ? ? ? ?//注冊字符驅動



執行完次就可以將我們的驅動程序加載到內核里了


首先我們搭建主程序,字符驅動的名字就叫做"main"


首先先寫下將要用到的頭文件,以及一個宏定義,指明了我們驅動的名稱,當然名稱可以任意這里就取"main" 作為名字?


#include <linux/fs.h>
#include <linux/module.h>
#include <linux/coda.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#define MUDULE_NAME "main"


驅動由于需要加載到內核里,所以我們需要聲明一下我們驅動所遵循的協議,如果沒有申明,那么加載內核的時候系統會提示一段信息。我們按照內核的風格來,就加一個GPL協議吧


MODULE_LICENSE("GPL");


我們要想將我們的驅動注冊到內核里,就必須將我們的驅動本身作為一個抽象,抽象成一個struct cdev的結構體。因為我們系統內部有許多中字符驅動,為了將這些不同種類的驅動都能使用同一個函數進行注冊,內核聲明了一個結構體,不同的驅動通過這個結構體--變成了一個抽象的驅動供系統調用。這段有點羅嗦,我們來看一下cdev這個結構體吧。


//這段不屬于主程序
struct cdev {
? ? struct kobject kobj;
? ? struct module *owner;
? ? const struct file_operations *ops;
? ? struct list_head list;
? ? dev_t dev;
? ? unsigned int count;
};


這個結構體就包含了一個驅動所應有的東西其中 kobj 不需要管它,我也沒有仔細研究,owner指向模塊的所有者,常常使用THIS_MODULE這個宏來賦值,ops是我們主要做的工作,其中定義了各種操作的接口。


下面我們定義了我們程序的抽象體mydev,以及他所需要的接口


struct cdev mydev;
struct file_operations ops;


struct file_operations這個結構有點龐大。


//不屬于本程序
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 *);
? ? 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 *, loff_t, loff_t, int datasync);
? ? int (*aio_fsync) (struct kiocb *, int datasync);
? ? int (*fasync) (int, struct file *, int);
? ? int (*lock) (struct file *, int, struct file_lock *);
? ? ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
? ? unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
? ? int (*check_flags)(int);
? ? int (*flock) (struct file *, int, struct file_lock *);
? ? ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
? ? ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
? ? int (*setlease)(struct file *, long, struct file_lock **);
? ? long (*fallocate)(struct file *file, int mode, loff_t offset,
? ? ? ? ? ? ? loff_t len);
};


上面看到,這個結構內部都是一些函數指針,相當與這個結構本身就是一個接口,在c語言中沒有接口這個概念,使用這種方式來定義也是一種巧妙的用法。不過有所不同的是我們可以不完全實現其中的接口。


應用程序在使用驅動的時候常常需要open,write,read,close這幾種操作,也就對應了file_operations結構中的open,write,read,release這幾個函數指針。下面我們開始實現我們自己的函數體。注意:我們自己實現的函數必須滿足接口函數所定義的形式。


static int main_open(struct inode* inode,struct file* filp)
{
? ? return 0;
}


這個教程里面的程序,我們就讓驅動只能往里面寫一個字符為例,讀取也是只能讀取一個字符。


?我們定義一個靜態的字符類型的變量來當作我們的存儲空間,通過copy_from_user來將用戶空間的數據拷貝到我們驅動 ?所在的內核空間。原型是:?


static inline long copy_from_user(void *to, const void __user * from, unsigned long n)


?類似地,我們使用copy_to_user來完成內核空間到用戶空間的數據拷貝。


static inline long copy_to_user(void __user *to,const void *from, unsigned long n)


static char main_buffer;
static ssize_t main_write(struct file* filp,const char __user* buffer,size_t length,loff_t * l)
{
? ? if(length!=1) ?return -1;
? ? copy_from_user(&main_buffer,buffer,length);
? ? return 1;
}


下面是讀的實現


static ssize_t main_read(struct file* filp,char __user * buffer,size_t length,loff_t*l)
{
? ? if(length!=1) return -1;
? ? copy_to_user(buffer,&main_buffer,length);
? ? return 1;
}


再稍稍實現一下close


static int main_close(struct inode* inode,struct file* filp)
{
? ? return 0;
}?


我們所需要的內容都已經填寫完畢,我們在驅動初始化的時候調用cdev_add驅動注冊到系統就行了,不過在注冊之前我們要申請設備號。


static dev_t dev;
static int __init main_init(void)
{?


首先我們使用動態申請的方式申請設備號


int result=alloc_chrdev_region(&dev,0,1,MODULE_NAME);


dev就是我們申請的設備號,其中dev_t其實就是一個無符號的long型,通過調用alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *) 將申請到的設備號寫入到dev中,第二個參數是子設備號從幾開始,第三個參數是申請幾個設備號,因為申請多個設備號是連續的,所以我們只需要知道第一個就行了。第四個參數代表我們驅動的名稱。


返回值如果小于0則表示我們申請失敗,通過printk打印錯誤信息。我測試的在動態加載的時候printk都不能打印其信息,如果在Ubuntu下可以查看/var/log/kern.log,如果是CentOS下可以查看/var/log/mssages來查看printk打印的信息,一般查看后10條就能足夠了。


? ? ? ? if(result<0)
? ? {
? ? ? ? printk(KERN_ALERT"device load error");
? ? ? ? return -1;
? ? }
?


然后我們再構造一下我們的接口結構體


? ? ops.owner=THIS_MODULE;
? ? ops.open=main_open;
? ? ops.release=main_close;
? ? ops.write=main_write;
? ? ops.read=main_read;


構造完之后,我們就只剩下我們最重要的一步了,就是向系統注冊我們的驅動。


不過,先別急,我們注冊前得先把我們的抽象驅動mydev給構造了,mydev的定義在最上面。


cdev_init(&mydev,&ops);
mydev.owner=THIS_MODULE;


這樣,我們就使用了我們的ops構造了我們的抽象驅動,當我們把這個驅動添加到我們的內核里面的時候,假如內核想對這個驅動進行寫的操作,就會從mydev->ops->main_write這樣找到我們自己實現的寫函數了。


接下來就注冊我們的驅動。


? ? ? ? cdev_add(&mydev,dev,1);
? ? ? ? printk(KERN_ALERT"device load success\n");
? ? ? ? return 0;
}


至此,我們的驅動就算完成了,不過有一點,就是我們的驅動有了初始化函數了就一定還有一個清理的函數了,該函數主要在我們卸載驅動的時候會調用,module_init及module_exit主要用于聲明這個驅動的入口與出口,是必須要做的一步。


static void __exit main_exit(void)
{
? ? ? ? unregister_chrdev_region(dev,1);
? ? cdev_del(&mydev);
}
module_init(main_init);
module_exit(main_exit);
?

我的Makefile是下面這樣

ifeq ($(KERNELRELEASE),)
KERNELDIR?=/lib/modules/$(shell uname -r)/build
PWD :=$(shell pwd)
modules:
? ? $(MAKE) -C $(KERNELDIR) M=$(PWD) modules -Wall
modules_install:
? ? $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install -Wall
clean:
? ? rm -rf *.0 *~ core .depend .*.cmd
? ? sudo rmmod main
? ? sudo rm /dev/main
install:
? ? sudo insmod main.ko
? ? sudo mknod /dev/main c 250 0
message:
? ? tail -n 5 /var/log/kern.log
.PHONY: modules modules_install clean
else
? ? obj-m :=main.o
endif


因為我的機器是使用ubuntu,所以在message標簽下是tail -n 5 /var/log/kern.log 如果的/var/log目錄下沒有kern.log,那么就替換成/var/log/messages


編譯


$make


因為這個Makefile的install 都是針對我自己電腦而寫的,所以并不能在你電腦上保證執行make install 的正確性。還是在命令行中敲吧


sudo insmod main.ko


安裝模塊,然后查看/proc/devices里面main這個模塊對應的設備號是多少,我的是250,所以創建設備節點時這樣創建


sudo mknod /dev/main c 250 0


這樣就成功把我們的驅動安裝到內核里了


執行make message可以看到如下信息


tail -n 5 /var/log/kern.log
Sep 17 20:05:57 quanweiC kernel: [23536.688371] [UFW BLOCK] IN=wlan0 OUT= MAC=c0:18:85:73:a1:8e:b8:88:e3:eb:30:c3:08:00 SRC=192.168.1.103 DST=192.168.1.105 LEN=63 TOS=0x00 PREC=0x00 TTL=64 ID=2558 DF PROTO=UDP SPT=11818 DPT=26724 LEN=43?
Sep 17 20:06:02 quanweiC kernel: [23541.691748] [UFW BLOCK] IN=wlan0 OUT= MAC=c0:18:85:73:a1:8e:b8:88:e3:eb:30:c3:08:00 SRC=192.168.1.103 DST=192.168.1.105 LEN=63 TOS=0x00 PREC=0x00 TTL=64 ID=3335 DF PROTO=UDP SPT=11818 DPT=26724 LEN=43?
Sep 17 20:06:51 quanweiC kernel: [23590.610275] [UFW BLOCK] IN=wlan0 OUT= MAC=c0:18:85:73:a1:8e:b8:88:e3:eb:30:c3:08:00 SRC=192.168.1.103 DST=192.168.1.105 LEN=63 TOS=0x00 PREC=0x00 TTL=64 ID=10708 PROTO=UDP SPT=11818 DPT=10948 LEN=43?
Sep 17 20:07:04 quanweiC kernel: [23603.815562] [UFW BLOCK] IN=wlan0 OUT= MAC=c0:18:85:73:a1:8e:b8:88:e3:eb:30:c3:08:00 SRC=192.168.1.103 DST=192.168.1.105 LEN=63 TOS=0x00 PREC=0x00 TTL=64 ID=12523 PROTO=UDP SPT=11818 DPT=10104 LEN=43?
Sep 17 20:07:04 quanweiC kernel: [23603.930248] device load success


最后一行顯示我們加載驅動成功了。這樣一個簡單的字符驅動就寫成功了。


Linux字符驅動中動態分配設備號與動態生成設備節點 http://www.linuxidc.com/Linux/2014-03/97438.htm


字符驅動設計----mini2440 LED驅動設計之路 http://www.linuxidc.com/Linux/2012-08/68706.htm


Linux 設備驅動 ====> 字符驅動 http://www.linuxidc.com/Linux/2012-03/57581.htm


如何編寫Ubuntu字符驅動 http://www.linuxidc.com/Linux/2010-05/25887.htm


2.4下內核linux字符驅動模板 http://www.linuxidc.com/Linux/2007-06/5338.htm


========

linux字符設備驅動開發基礎知識


Linux設備分類
Linux下的設備通常分為三類,字符設備,塊設備和網絡設 備。


字符設備


一個字符設 備是一種字節流設備,對設備的存取只能按順序按字節的存取而不能隨機訪問,字符設備沒有請求緩沖區,所有的訪問請求都是按順序執行的。Linux下的大多設備都是字符設備。應用程序是通過字符設備節點來訪問 字符設備的。設備節點一般都由mknod命令都創 建在/dev目錄下,下 面的例子顯示了串口設備的設備節點。字符設備文件的第一個標志是前面的“c”標志。


root#ls -l /dev/ttyS[0-3]
crw-rw---- ?1 root ?root 4, 64 Feb 18 23:34 /dev/ttyS0
crw-r----- ?1 root ?root 4, 65 Nov 17 10:26 /dev/ttyS1
crw-rw---- ?1 root ?root 4, 66 Jul ?5 ?2000 /dev/ttyS2
crw-rw---- ?1 root ?root 4, 67 Jul ?5 ?2000 /dev/ttyS3


字符設備是指那些只能按順序一個字節一個字節讀取的設備,但事實上現在一些高級 字符設備也可以從指定位置一次讀取一塊數據。字符設備是面向數據流的設備,每個字符設備都有一個設備號,設備號由主設備號和次設備號組成。同時Linux使用管理文件相同的方法來管理字符設備,所以每個字符設備在/dev/目錄下都有一個對應的設備文件,即設備節點,它們包含了設備的 類型、主/次設備號以 及設備的訪問權限控制等,系統通過設備文件來對字符設備進行操作,每個字符設備文件都有自己的與普通文件相同的文件操作函數組結構(struct file_operations)。字符設 備驅動通常至少需要實現文件操作函數組中的open、release、read和write四種操作方法。常見的字符設備有鼠標、鍵盤、串口、控制臺等。


塊設備


存儲設備一 般屬于塊設備,塊設備有請求緩沖區,并且支持隨機訪問而不必按照順序去存取數據,比如你可以 先存取后面的數據,然后在存取前面的數據,這對字符設備來說是不可能的。Linux下的磁盤 設備都是塊設備,盡管在Linux下有塊設 備節點,但應用程序一般是通過文件系統及其高速緩存來訪問塊設備的,而不是直 接通過設備節點來讀寫塊設備上的數據。塊設備文件的第一個標志是前面的“b”標志。


root# ls -l /dev/hda[1-3]
brw-rw---- ?1 root ?root ?3, 1 Jul ?5 ?2000 /dev/hda1
brw-rw---- ?1 root ?root ?3, 2 Jul ?5 ?2000 /dev/hda2
brw-rw---- ?1 root ?root ?3, 3 Jul ?5 ?2000 /dev/hda3


塊設備是指那些可以從設備的任意位置讀取任意長度數據的設備。每個塊設備同樣有 一個設備號,設備號由主設備號和次設備號組成。同時Linux也使用管 理文件相同的方法來管理塊設備,每個塊設備在/dev/目錄下都 有一個對應的設備文件,即設備節點,它們包含了設備的類型、主/次設備號 以及設備的訪問權限控制等,系統通過設備文件來對塊設備進行操作,每個塊設備文件都有自己的與普通文件相同的文件操作函數組結構(struct file_operations)。但塊設 備需要實現的操作方法遠比字符設備的操作方法多得多,也難得多。塊設備既可以作為普通的裸設備用來存放任意數據,也可以將塊設備按某種文件系統類型的格式 進行格式化,然后按照該文件系統類型的格式來讀取塊設備上的數據,但不管哪種方式,最后訪問設備上的數據都必須通過調用設備本身的操作方法實現,區別在于 前者直接調用塊設備的操作方法,而后者則間接調用塊設備的操作方法。常見的塊設備有各種硬盤、flash磁盤、RAM磁盤等。


網絡設備


網絡設備不 同于字符設備和塊設備,它是面向報文的而不是面向流的,它不支持隨機訪問,也沒有請求緩沖區。在Linux里一個網絡設備也可以叫做一個網絡接口,如eth0,應用程序是通過Socket而不是設備節點來訪問網絡設備,在系統里根本就不存在網絡設備節點。


網絡接口用來與其他設備交換數據,它可以是硬件設備,也可以是純軟件設備,如loopback接口就是一個純軟件設備。網絡接口由內核中的網絡 子系統驅動,負責發送和接收數據包,但它不需要了解每項事務如何映射到實際傳送的數據包,許多網絡連接(尤其是使用TCP協議的連接)是面向流的,但網絡設備圍繞數據包的傳輸和接收設 計。網絡驅動程序不需要知道各個連接的相關信息,它只需處理數據包。網絡接口沒有像字符設備和塊設備一樣的設備號,只有一個唯一的名字,如eth0、eth1等,而這個名字也不需要與設備文件節點對應。內核使用一套與數據 包傳輸相關的函數來與網絡設備驅動程序通信,它們不同于字符設備和塊設備的read()和write()方法。




設備節點、設備驅動及設備的關聯
? ? ?當我們訪問 一個設備節點是,系統是如果知道使用哪個設備驅動及訪問哪個設備的呢?這個是通過設備號來實現的。當我們創建一個設備節點時需要指定主設備號和次設備號。 對于設備節點來說,名字不是重要的,設備號才是最重要的,它實際指定了對應的驅動程序和對應的設備。


Linux的設備管理是和文件系統緊密結合的,各種設備都以文件的形式存 放在/dev目錄下,稱 為設備文件。應用程序可以打開、關閉和讀寫這些設備文件,完成對設備的操作,就像操作普通的數據文件一樣。為了管理這些設備,系統為設備編了號,每個設備 號又分為主設備號和次設備號。主設備號用來區分不同種類的設備,而次設備號用來區分同一類型的多個設備。對于常用設備,Linux有約定俗成的編號,如硬盤的主設備號是3。


?


? ? ? Linux為所有的 設備文件都提供了統一的操作函數接口,方法是使用數據結構struct file_operations。這個數據結構中包括許多操作函數的指針,如open()、close()、read()和write()等,但由于外設的種類較多,操作方式各不相同。Struct file_operations結構體中的 成員為一系列的接口函數,如用于讀/寫的read/write函數和用于控制的ioctl等。打開一個文件就是調用這個文件file_operations中的open操作。不同類型的文件有不同的file_operations成員函數, 如普通的磁盤數據文件,接口函數完成磁盤數據塊讀寫操作;而對于各種設備文件,則最終調用各自驅動程序中的I/O函數進行具體設備的操作。這樣,應用程序根本不必考慮操作的是設 備還是普通文件,可一律當作文件處理,具有非常清晰統一的I/O接口。所 以file_operations是文件層 次的I/O接口。


?


主設備號


驅動程序在 初始化時,會注冊它的驅動及對應主設備號到系統中,這樣當應用程序訪問設備節點時,系統就知道它所訪問的驅動程序了。你可以通過/proc/devices文件來驅動 系統設備的主設備號。


次設備號


驅動程序遍 歷設備時,每發現一個它能驅動的設備,就創建一個設備對象,并為其分配一個次設備號以區分不同的設備。這樣當應用程序訪問設備節點時驅動程序就可以根據次 設備號知道它說訪問的設備了。


系統中的每一個字符設備和塊設備(網絡接口沒有設備號)都有一個設備號,傳統的UNIX以及早期版本Linux中的設備號是16位的,主次設備號都是8位的,低8位為次設備號,高8位為主設備號,因此系統最多分別支持65536個字符設備和65536個塊設備,這個限制已經不能滿足當 前層出不窮的各種新設備的需要,所以Linux2.6中對設備號已經進行了擴展,一個設備 號為32位,主設備號為12位,次設備號為20位,但是這32位設備號的編碼方式有新舊兩種,舊的 設備編號格式為:最高12位為主設備號,最低20位為次設備號;新的設備編號格式為:bit[19:8]是主設備號,bit[31:20]是次設備號的高12位,bit[7:0]是次設備號的低8位。如果知道了一個設備的主設備號major和次設備號minor,那么用MKDEV(major,minor)生成是該設備的舊格式的設備號,用new_encode_dev(MKDEV(major,minor))生成的則是新格式的設備號。Linux支持的各種設備的主設備號定義在include/linux/major.h文件中,而已經在官方注冊的主設備 號和次設備號在Documentation/devices.txt文件中可以找到。


老式16位設備 號、32位舊格式設 備號以及32位新格式設 備號的轉換操作函數如下:


new_encode_dev(dev_t dev)函數


將32位舊格式 設備號dev轉換成32位新格式設備號。


new_decode_dev(u32 dev)函數


將32位新格式 設備號轉換成32位舊格式設 備號。


old_encode_dev(dev_t dev)函數


將32位舊格式 設備號轉換成老式16位設備號。


dev_t old_decode_dev(u16 val)函數


將老式16位設備號轉換成32位舊格式設備號。


Linux中設備節點是通過“mknod”命令來創建 的。一個設備節點其實就是一個文件,Linux中稱為設 備文件。有一點必要說明的是,在Linux中,所有 的設備訪問都是通過文件的方式,一般的數據文件程序普通文件,設備節點稱為設備文件。在Linux內核中網絡設備也是通過文件操作的,稱為網絡設備文件,在用戶空間是通過socket接口來訪問的。socket號就是網絡設備文件描述符。


如:mknod /dev/mydevice c 254 0


(c代表子都設備,254為主設備號,0為次設備號)


Open,close等操作/dev/下設備文件,內核根據文件的主設備號找到對應的設備驅動


主設備號可以分為動態和靜態申請。


設備文件


Linux使用對文 件一樣管理方式來管理設備,所以對于系統中的每個字符設備或者塊設備都必須為其創建一個設備文件,這個設備文件就是放在/dev/目錄下的設備節點,它包含了該設備的設備類型(塊設備或字符設 備)、設備號(主設備號和次設備號)以及設備訪問控制屬性等。設備文件可以通過手工用mknod命令生成也可以由udev用戶工具 軟件在系統啟動后根據/sys目錄下每個 設備的實際信息創建,使用后一種方式可以為每個設備動態分配設備號,而不必分配固定的設備號,如果系統中的設備不多,而且設備類型又是常見的,可以使用手 工方式生成設備文件,為常用設備創建一個已經分配號的設備號對應的設備文件,這樣比較方便。如果系統很大,系統中的設備太多,那么最好動態分配設備號,由udev在系統啟動之后根據設備實際信息自動創建設備文件。


Linux下的大部分驅動程序都是字符設備驅動程序,通過下面的學習我們將 會了解到字符設備是如何注冊到系統中的,應用程序是如何訪問驅動程序的數據的,及字符驅動程序是如何工作的。




設備號
通過前面的 學習我們知道應用程序是通過設備節點來訪問驅動程序及設備的,其根本是通過設備節點的設備號(主設備號及從設備號)來關聯驅動程序及設備的,字符設備也不 例外(其實字符設備只能這樣訪問)。這里我們詳細討論Linux內部如何管 理設備號的。


設備號類型
Linux內核里用“dev_t”來表示設備 號,它是一個32位的無符號 數,其高12位用來表示主 設備號,低20位用來表示從 設備號。它被定義在<linux/types.h>頭文件里。 內核里提供了操作“dev_t”的函數,驅動 程序中通過這些函數(其實是宏,定義在<linux/kdev_t.h>文件中)來 操作設備號。


#define MINORBITS ? ?20
#define MINORMASK ? ?((1U << MINORBITS) - 1)
#define MAJOR(dev) ? ?((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ? ?((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) ? ?(((ma) << MINORBITS) | (mi))




MAJOR(dev)用于獲取主設備號,MINOR(dev)用于獲取從設備號,而MKDEV(ma,mi)用于通過主設備號和從設備號構造"dev_t"數據。
另一點需要 說明的是,dev_t數據類型支持2^12個主設備號,每個主設備號(通常是一個設備驅動)可以支持2^20個設備,目前來說這已經足夠大了,但誰又能說將來還能滿足要求 呢?一個良好的編程習慣是不要依賴dev_t這個數據類 型,切記必須使用內核提供的操作設備號的函數。


字符設備號注冊
內核提供了字符設備號管理的函數接口,作為一個良好的編程習慣,字符設備驅動程 序應該通過這些函數向系統注冊或注銷字符設備號。


int register_chrdev_region(dev_t from, unsigned count, const char *name)
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
? ? ? ? ? ? const char *name)
void unregister_chrdev_region(dev_t from, unsigned count)




register_chrdev_region用于向內核 注冊已知可用的設備號(次設備號通常是0)范圍。由 于歷史的原因一些設備的設備號是固定的,你可以在內核源代碼樹的Documentation/devices.txt文件中找到 這些靜態分配的設備號。


alloc_chrdev_region用于動態分 配的設備號并注冊到內核中,分配的設備號通過dev參數返回。 作為一個良好的內核開發習慣,我們推薦你使用動態分配的方式來生成設備號。
unregister_chrdev_region用于注銷一 個不用的設備號區域,通常這個函數在驅動程序卸載時被調用。




字符設備
Linux2.6內核使用“struct cdev”來記錄字符設 備的信息,內核也提供了相關的函數來操作“struct cdev”對象,他們 定義在<linux/cdev.h>頭文件中。 可見字符設備及其操作函數接口定義的很簡單。


struct cdev {
? ? struct kobject kobj;
? ? struct module *owner;
? ? const struct file_operations *ops;
? ? struct list_head list;
? ? dev_t dev;
? ? unsigned int count;
};


void cdev_init(struct cdev *, const struct file_operations *);
struct cdev *cdev_alloc(void);
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);




對于Linux 2.6內核來說,struct cdev是內核字符設備的基礎結構,用來表示一個字符設備, 包含了字符設備需要的全部信息。


kobj:struct kobject對象數據,用 來描述設備的引用計數,是Linux設備模型的 基礎結構。我們在后面的“Linux設備模型”在做詳細的介紹。
owner:struct module對象數據,描 述了模塊的屬主,指向擁有這個結構的模塊的指針,顯然它只有對編譯為模塊方式的驅動才由意義。一般賦值位“THIS_MODULE”。
ops:struct file_operations對象數據,描 述了字符設備的操作函數指針。對于設備驅動來說,這是一個很重要的數據成員,幾乎所有的驅動都要用到這個對象,我們會在下面做詳細介紹。
dev:dev_t對象數據,描述了字符設備的設備號。
內核提供了操作字符設備對象“struct cdev”的函數,我們 只能通過這些函數來操作字符設備,例如:初始化、注冊、添加、移除字符設備。


cdev_alloc:用于動態 分配一個新的字符設備 cdev 對象,并對其 進行初始化。采用cdev_alloc分配的cdev對象需要顯示的初始化owner和ops對象。
// 參考drivers/scsi/st.c:st_probe 函數
struct cdev *cdev = NULL;
cdev = cdev_alloc();
// Error Processing
cdev->owner = THIS_MODULE;
cdev->ops = &st_fops;

?


cdev_init:用于初始 化一個靜態分配的cdev對象,一般這 個對象會嵌入到其他的對象中。cdev_init會自動初始 化ops數據,因此應 用程序只需要顯示的給owner對象賦值。cdev_init的功能與cdev_alloc基本相同,唯 一的區別是cdev_init初始化一個 已經存在的cdev對象,并且這 個初始化會影響到字符設備刪除函數(cdev_del)的行為, 請參考cdev_del函數。
cdev_add:向內核系 統中添加一個新的字符設備cdev,并且使它立 即可用。
cdev_del:從內核系 統中移除cdev字符設備。如 果字符設備是由cdev_alloc動態分配的, 則會釋放分配的內存。
cdev_put:減少模塊 的引用計數,一般很少會有驅動程序直接調用這個函數。
文件操作對象
Linux中的所有設備都是文件,內核中用“struct file”結構來表示一 個文件。盡管我們的驅動不會直接使用這個結構中的大部分對象,其中的一些數據成員還是很重要的,我們有必要在這里做一些介紹,具體的內容請參考內核源代碼 樹<linux/fs.h>頭文件。


// struct file 中的一些重要數據成員
const struct file_operations ? ?*f_op;
unsigned int ? ? ? ? f_flags;
mode_t ? ? ? ? ? ?f_mode;
loff_t ? ? ? ? ? ?f_pos;
struct address_space ? ?*f_mapping;



這里我們不對struct file做過多的介紹,另一篇struct file將做詳細介紹。這個結構中的f_ops成員是我們的驅動所關心的,它是一個struct file_operations結構。Linux里的struct file_operations結構描述了一 個文件操作需要的所有函數,它定義在<linux/fs.h>頭文件中。


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 (*lock) (struct file *, int, struct file_lock *);
? ? ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
? ? unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
? ? int (*check_flags)(int);
? ? int (*dir_notify)(struct file *filp, unsigned long arg);
? ? int (*flock) (struct file *, int, struct file_lock *);
? ? ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
? ? ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
? ? int (*setlease)(struct file *, long, struct file_lock **);
};




這是一個很大的結構,包含了所有的設備操作函數指針。當然,對于 一個驅動,不是所有的接口都需要來實現的。對于一個字符設備來說,一般實現open、release、read、write、mmap、ioctl這幾個函數就足夠了。
這里需要指 出的是,open和release函數的第一個參數是一個struct inode對象。這是一個內核文件系統索引節點對象,它包含 了內核在操作文件或目錄是需要的全部信息。對于字符設備驅動來說,我們關心的是從struct inode對象中獲取設備號(inode的i_rdev成員)內核提供了兩個函數來做這件事。


static inline unsigned iminor(const struct inode *inode)
{
? ? return MINOR(inode->i_rdev);
}
static inline unsigned imajor(const struct inode *inode)
{
? ? return MAJOR(inode->i_rdev);
}




盡管我們可 以直接從inode->i_rdev獲取設備 號,但是盡量不要這樣做。我們推薦你調用內核提供的函數來獲取設備號,這樣即使將來inode->i_rdev有所變化, 我們的程序也會工作的很好。


?


字符設備驅 動可以參考Linux 設備驅動程序 第三版和linux設備驅動開發 詳解,其中linux設備驅動程序 第三版中講的:


主次編號


一些重要數據結構


字符設備注冊


Open和release


讀和寫


一些頭文件和結構體;


都非常經典, 都理解字符驅動設備很重要,很值得參考!


http://blog.csdn.net/shanzhizi
========

?LINUX字符設備驅動程序實例(scull)



該驅動程序在UBUNTU10.04LTS編譯通過,系統內核為linux-2.6.32-24(可使用uname -r 命令來查看當前內核的版本號)
由于安裝UBUNTU10.04LTS時,沒有安裝LINUX內核源碼,因此需要在www.kernel.org下載LINUX源碼,下載linux-2.6.32.22.tar.bz2(與系統運行的LINUX內核版本盡量保持一致),使用如下命令安裝內核:
1.解壓內核
cd /us/src


tar jxvf linux-2.6.32.22.tar.bz2


2.為系統的include創建鏈接文件
cd /usr/include
rm -rf asm linux scsi
ln -s /usr/src/linux-2.6.32.22/include/asm-generic asm
ln -s /usr/src/linux-2.6.32.22/include/linux linux
ln -s /usr/src/linux-2.6.32.22/include/scsi scsi


LINUX內核源碼安裝完畢
【2.驅動程序代碼】
/******************************************************************************
*Name: memdev.c
*Desc: 字符設備驅動程序的框架結構,該字符設備并不是一個真實的物理設備,
* 而是使用內存來模擬一個字符設備
*Parameter:?
*Return:
*Author: yoyoba(stuyou@126.com)
*Date: 2010-9-26
*Modify: 2010-9-26
********************************************************************************/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>


#include "memdev.h"


static mem_major = MEMDEV_MAJOR;


module_param(mem_major, int, S_IRUGO);


struct mem_dev *mem_devp; /*設備結構體指針*/


struct cdev cdev;?


/*文件打開函數*/
int mem_open(struct inode *inode, struct file *filp)
{
? ? struct mem_dev *dev;
? ??
? ? /*獲取次設備號*/
? ? int num = MINOR(inode->i_rdev);


? ? if (num >= MEMDEV_NR_DEVS)?
? ? ? ? ? ? return -ENODEV;
? ? dev = &mem_devp[num];
? ??
? ? /*將設備描述結構指針賦值給文件私有數據指針*/
? ? filp->private_data = dev;
? ??
? ? return 0;?
}


/*文件釋放函數*/
int mem_release(struct inode *inode, struct file *filp)
{
? return 0;
}


/*讀函數*/
static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
? unsigned long p = *ppos;
? unsigned int count = size;
? int ret = 0;
? struct mem_dev *dev = filp->private_data; /*獲得設備結構體指針*/


? /*判斷讀位置是否有效*/
? if (p >= MEMDEV_SIZE)
? ? return 0;
? if (count > MEMDEV_SIZE - p)
? ? count = MEMDEV_SIZE - p;


? /*讀數據到用戶空間*/
? if (copy_to_user(buf, (void*)(dev->data + p), count))
? {
? ? ret = - EFAULT;
? }
? else
? {
? ? *ppos += count;
? ? ret = count;
? ??
? ? printk(KERN_INFO "read %d bytes(s) from %d\n", count, p);
? }


? return ret;
}


/*寫函數*/
static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
? unsigned long p = *ppos;
? unsigned int count = size;
? int ret = 0;
? struct mem_dev *dev = filp->private_data; /*獲得設備結構體指針*/
??
? /*分析和獲取有效的寫長度*/
? if (p >= MEMDEV_SIZE)
? ? return 0;
? if (count > MEMDEV_SIZE - p)
? ? count = MEMDEV_SIZE - p;
? ??
? /*從用戶空間寫入數據*/
? if (copy_from_user(dev->data + p, buf, count))
? ? ret = - EFAULT;
? else
? {
? ? *ppos += count;
? ? ret = count;
? ??
? ? printk(KERN_INFO "written %d bytes(s) from %d\n", count, p);
? }


? return ret;
}


/* seek文件定位函數 */
static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
{?
? ? loff_t newpos;


? ? switch(whence) {
? ? ? case 0: /* SEEK_SET */
? ? ? ? newpos = offset;
? ? ? ? break;


? ? ? case 1: /* SEEK_CUR */
? ? ? ? newpos = filp->f_pos + offset;
? ? ? ? break;


? ? ? case 2: /* SEEK_END */
? ? ? ? newpos = MEMDEV_SIZE -1 + offset;
? ? ? ? break;


? ? ? default: /* can't happen */
? ? ? ? return -EINVAL;
? ? }
? ? if ((newpos<0) || (newpos>MEMDEV_SIZE))
? ? ?return -EINVAL;
? ? ?
? ? filp->f_pos = newpos;
? ? return newpos;


}


/*文件操作結構體*/
static const struct file_operations mem_fops =
{
? .owner = THIS_MODULE,
? .llseek = mem_llseek,
? .read = mem_read,
? .write = mem_write,
? .open = mem_open,
? .release = mem_release,
};


/*設備驅動模塊加載函數*/
static int memdev_init(void)
{
? int result;
? int i;


? dev_t devno = MKDEV(mem_major, 0);


? /* 靜態申請設備號*/
? if (mem_major)
? ? result = register_chrdev_region(devno, 2, "memdev");
? else /* 動態分配設備號 */
? {
? ? result = alloc_chrdev_region(&devno, 0, 2, "memdev");
? ? mem_major = MAJOR(devno);
? }?
??
? if (result < 0)
? ? return result;


? /*初始化cdev結構*/
? cdev_init(&cdev, &mem_fops);
? cdev.owner = THIS_MODULE;
? cdev.ops = &mem_fops;
??
? /* 注冊字符設備 */
? cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);
? ?
? /* 為設備描述結構分配內存*/
? mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
? if (!mem_devp) /*申請失敗*/
? {
? ? result = - ENOMEM;
? ? goto fail_malloc;
? }
? memset(mem_devp, 0, sizeof(struct mem_dev));
??
? /*為設備分配內存*/
? for (i=0; i < MEMDEV_NR_DEVS; i++)?
? {
? ? ? ? mem_devp[i].size = MEMDEV_SIZE;
? ? ? ? mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);
? ? ? ? memset(mem_devp[i].data, 0, MEMDEV_SIZE);
? }
? ??
? return 0;


? fail_malloc:?
? unregister_chrdev_region(devno, 1);
??
? return result;
}


/*模塊卸載函數*/
static void memdev_exit(void)
{
? cdev_del(&cdev); /*注銷設備*/
? kfree(mem_devp); /*釋放設備結構體內存*/
? unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*釋放設備號*/
}


MODULE_AUTHOR("David Xie");
MODULE_LICENSE("GPL");


module_init(memdev_init);
module_exit(memdev_exit);


/************************
*memdev.h
************************/
#ifndef _MEMDEV_H_
#define _MEMDEV_H_


#ifndef MEMDEV_MAJOR
#define MEMDEV_MAJOR 260 /*預設的mem的主設備號*/
#endif


#ifndef MEMDEV_NR_DEVS
#define MEMDEV_NR_DEVS 2 /*設備數*/
#endif


#ifndef MEMDEV_SIZE
#define MEMDEV_SIZE 4096
#endif


/*mem設備描述結構體*/
struct mem_dev?
{?
? char *data;?
? unsigned long size;?
};


#endif /* _MEMDEV_H_ */


【3.編譯驅動程序模塊】
Makefile文件的內容如下:
ifneq ($(KERNELRELEASE),)


obj-m:=memdev.o


else


KERNELDIR:=/lib/modules/$(shell uname -r)/build


PWD:=$(shell pwd)


default:


?$(MAKE) -C $(KERNELDIR) M=$(PWD) modules


clean:


?rm -rf *.o *.mod.c *.mod.o *.ko


endif


切換到root下,執行make時,如果UBUNTU是使用虛擬機安裝的,那么執行make時,不要在ubuntu和windows的共享目錄下,否則會出錯。
root@VMUBUNTU:~# make
make -C /lib/modules/2.6.32-24-generic/build M=/root modules
make[1]: Entering directory `/usr/src/linux-headers-2.6.32-24-generic'
? CC [M] /root/memdev.o
/root/memdev.c:15: warning: type defaults to ‘int’ in declaration of ‘mem_major’
/root/memdev.c: In function ‘mem_read’:
/root/memdev.c:71: warning: format ‘%d’ expects type ‘int’, but argument 3 has type ‘long unsigned int’
/root/memdev.c: In function ‘mem_write’:
/root/memdev.c:99: warning: format ‘%d’ expects type ‘int’, but argument 3 has type ‘long unsigned int’
? Building modules, stage 2.
? MODPOST 1 modules
? CC /root/memdev.mod.o
? LD [M] /root/memdev.ko
make[1]: Leaving directory `/usr/src/linux-headers-2.6.32-24-generic'


ls查看當前目錄的內容
root@VMUBUNTU:~# ls
Makefile memdev.h memdev.mod.c memdev.o Module.symvers
memdev.c memdev.ko memdev.mod.o modules.order


這里的memdev.ko就是生成的驅動程序模塊。
通過insmod命令把該模塊插入到內核
root@VMUBUNTU:~# insmod memdev.ko


查看插入的memdev.ko驅動
root@VMUBUNTU:~# cat /proc/devices
Character devices:
? 1 mem
? 4 /dev/vc/0
? 4 tty
? 4 ttyS
? 5 /dev/tty
? 5 /dev/console
? 5 /dev/ptmx
260 memdev
? 6 lp
? 7 vcs
?10 misc
?13 input
?14 sound
?21 sg
?29 fb
?99 ppdev
108 ppp
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
226 drm
251 hidraw
252 usbmon
253 bsg
254 rtc


Block devices:
? 1 ramdisk
259 blkext
? 7 loop
? 8 sd
? 9 md
?11 sr
?65 sd
?66 sd
?67 sd
?68 sd
?69 sd
?70 sd
?71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
252 device-mapper
253 pktcdvd
254 mdp


可以看到memdev驅動程序被正確的插入到內核當中,主設備號為260,該設備號為memdev.h中定義的#define MEMDEV_MAJOR 260。
如果這里定義的主設備號與系統正在使用的主設備號沖突,比如主設備號定義如下:#define MEMDEV_MAJOR 254,那么在執行insmod命令時,就會出現如下的錯誤:
root@VMUBUNTU:~# insmod memdev.ko
insmod: error inserting 'memdev.ko': -1 Device or resource busy


查看當前設備使用的主設備號
root@VMUBUNTU:~# cat /proc/devices
Character devices:
? 1 mem
? 4 /dev/vc/0
? 4 tty
? 4 ttyS
? 5 /dev/tty
? 5 /dev/console
? 5 /dev/ptmx
? 6 lp
? 7 vcs
?10 misc
?13 input
?14 sound
?21 sg
?29 fb
?99 ppdev
108 ppp
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
226 drm
251 hidraw
252 usbmon
253 bsg
254 rtc


Block devices:
? 1 ramdisk
259 blkext
? 7 loop
? 8 sd
? 9 md
?11 sr
?65 sd
?66 sd
?67 sd
?68 sd
?69 sd
?70 sd
?71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
252 device-mapper
253 pktcdvd
254 mdp


發現字符設備的254主設備號為rtc所使用,因此會出現上述錯誤,解決方法只需要在memdev.h中修改主設備號的定義即可。
【4.編寫應用程序,測試該驅動程序】
首先應該在/dev/目錄下創建與該驅動程序相對應的文件節點,使用如下命令創建:
root@VMUBUNTU:/dev# mknod memdev c 260 0


使用ls查看創建好的驅動程序節點文件
root@VMUBUNTU:/dev# ls -al memdev
crw-r--r-- 1 root root 260, 0 2010-09-26 17:28 memdev


編寫如下應用程序,來對驅動程序進行測試。
/******************************************************************************
*Name: memdevapp.c
*Desc: memdev字符設備驅動程序的測試程序。先往memedev設備寫入內容,然后再
* 從該設備中把內容讀出來。
*Parameter:?
*Return:
*Author: yoyoba(stuyou@126.com)
*Date: 2010-9-26
*Modify: 2010-9-26
********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <linux/i2c.h>
#include <linux/fcntl.h>


int main()
{
?int fd;
?char buf[]="this is a example for character devices driver by yoyoba!";//寫入memdev設備的內容


?char buf_read[4096]; //memdev設備的內容讀入到該buf中


?
?if((fd=open("/dev/memdev",O_RDWR))==-1) //打開memdev設備


? printf("open memdev WRONG!\n");
?else
? printf("open memdev SUCCESS!\n");
??
?printf("buf is %s\n",buf);?


?write(fd,buf,sizeof(buf)); //把buf中的內容寫入memdev設備


?
?lseek(fd,0,SEEK_SET); //把文件指針重新定位到文件開始的位置


?
?read(fd,buf_read,sizeof(buf)); //把memdev設備中的內容讀入到buf_read中


?
?printf("buf_read is %s\n",buf_read);
?
?return 0;
}


編譯并執行該程序
root@VMUBUNTU:/mnt/xlshare# gcc -o mem memdevapp.c


root@VMUBUNTU:/mnt/xlshare# ./mem
open memdev SUCCESS!
buf is this is a example for character devices driver by yoyoba!
buf_read is this is a example for character devices driver by yoyoba!


表明驅動程序工作正常。。。
【5.LINUX是如何make驅動程序模塊的】
Linux內核是一種單體內核,但是通過動態加載模塊的方式,使它的開發非常靈活 方便。那么,它是如何編譯內核的呢?我們可以通過分析它的Makefile入手。以下是 一個簡單的hello內核模塊的Makefile.?
ifneq ($(KERNELRELEASE),)
obj-m:=hello.o
else
KERNELDIR:=/lib/modules/$(shell uname -r)/build
PWD:=$(shell pwd)
default:
? ? ? ? $(MAKE) -C $(KERNELDIR) ?M=$(PWD) modules
clean:
? ? ? ? rm -rf *.o *.mod.c *.mod.o *.ko
endif
當我們寫完一個hello模塊,只要使用以上的makefile。然后make一下就行。 假設我們把hello模塊的源代碼放在/home/study/prog/mod/hello/下。 當我們在這個目錄運行make時,make是怎么執行的呢? LDD3第二章第四節“編譯和裝載”中只是簡略地說到該Makefile被執行了兩次, 但是具體過程是如何的呢??
首先,由于make 后面沒有目標,所以make會在Makefile中的第一個不是以.開頭 的目標作為默認的目標執行。于是default成為make的目標。make會執行 $(MAKE) -C $(KERNELDIR) M=$(PWD) modules shell是make內部的函數,假設當前內核版本是2.6.13-study,所以$(shell uname -r)的結果是 2.6.13-study 這里,實際運行的是?
make -C /lib/modules/2.6.13-study/build M=/home/study/prog/mod/hello/ modules
/lib/modules/2.6.13-study/build是一個指向內核源代碼/usr/src/linux的符號鏈接。 可見,make執行了兩次。第一次執行時是讀hello模塊的源代碼所在目錄/home/s tudy/prog/mod/hello/下的Makefile。第二次執行時是執行/usr/src/linux/下的Makefile時.?
但是還是有不少令人困惑的問題: 1.這個KERNELRELEASE也很令人困惑,它是什么呢?在/home/study/prog/mod/he llo/Makefile中是沒有定義這個變量的,所以起作用的是else…endif這一段。不 過,如果把hello模塊移動到內核源代碼中。例如放到/usr/src/linux/driver/中, KERNELRELEASE就有定義了。 在/usr/src/linux/Makefile中有 162 KERNELRELEASE=$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)$(LOCALVERSION) 這時候,hello模塊也不再是單獨用make編譯,而是在內核中用make modules進行 編譯。 用這種方式,該Makefile在單獨編譯和作為內核一部分編譯時都能正常工作。?
2.這個obj-m := hello.o什么時候會執行到呢? 在執行:?
make -C /lib/modules/2.6.13-study/build M=/home/study/prog/mod/hello/ modules
時,make 去/usr/src/linux/Makefile中尋找目標modules: 862 .PHONY: modules 863 modules: $(vmlinux-dirs) $(if $(KBUILD_BUILTIN),vmlinux) 864 @echo ' Building modules, stage 2.'; 865 $(Q)$(MAKE) -rR -f $(srctree)/scripts/Makefile.modpost?
可以看出,分兩個stage: 1.編譯出hello.o文件。 2.生成hello.mod.o hello.ko 在這過程中,會調用 make -f scripts/Makefile.build obj=/home/study/prog/mod/hello 而在 scripts/Makefile.build會包含很多文件: 011 -include .config 012 013 include $(if $(wildcard $(obj)/Kbuild), $(obj)/Kbuild, $(obj)/Makefile) 其中就有/home/study/prog/mod/hello/Makefile 這時 KERNELRELEASE已經存在。 所以執行的是: obj-m:=hello.o?
關于make modules的更詳細的過程可以在scripts/Makefile.modpost文件的注釋 中找到。如果想查看make的整個執行過程,可以運行make -n。
========

?Linux實現字符設備驅動的基礎步驟



Linux應用層想要操作kernel層的API,比如想操作相關GPIO或寄存器,可以通過寫一個字符設備驅動來實現。


1、先在rootfs中的 /dev/ 下生成一個字符設備。注意主設備號 和 從設備號。可用如下shell腳本生成:
[python] view plain copy
if [ ! -e audioIN ];then ?
? ? ?sudo mknod audioIN c 240 0 ? ? ??
fi ?


生成的設備為 /dev/audioIN ,主設備號240,從設備號0。


2、寫audioINdriver.ko ,audioINdriver.c 基本代碼框架如下:代碼中定義了設備名audioIN,設備號240, 0 ,與之前創建的設備一致。
[cpp] view plain copy
/**************************************************************************\?
?* audioINdriver.c?
?*?
?* kang_liu <liukang325@qq.com>?
?* 2014-07-15?
\**************************************************************************/ ?
??
#include <asm/uaccess.h> ?
#include <asm/errno.h> ?
#include <linux/types.h> ?
#include <linux/fs.h> ?
#include <linux/init.h> ?
#include <linux/module.h> ?
#include <linux/cdev.h> ?
#include <mach/gpio.h> ?
//#include <mach/at91_rstc.h> /* debug */ ?
//#include <mach/at91_pmc.h> ?
//#include <mach/at91_rstc.h> ?
//#include <mach/at91_shdwc.h> ?
#include <mach/irqs.h> ?
//#include "generic.h" ?
//#include "clock.h" ?
#include <mach/w55fa92_reg.h> ?
#include <asm/io.h> ?
??
#define DEV_MAJOR 240 ?
#define DEV_MINOR 0 ?
#define NUM_MINORS 1 ?
#define DEVICE_NAME "audioIN" ?
??
#define ERR(fmt, args...) printk(KERN_ALERT __FILE__ ": " fmt, ##args) ?
#define MSG(fmt, args...) printk(KERN_INFO __FILE__ ": " fmt, ##args) ?
#define DBG(fmt, args...) printk(KERN_DEBUG __FILE__ ": " fmt, ##args) ?
??
static ssize_t user_gpio_read(struct file *fp, char __user *buff, ?
? ? ? ? ? ? ? ? ? ? ? ?size_t count, loff_t *offp) ?
{ ?
? ? char str[32] = {0}; ?
? ? char out[32] = {0}; ?
? ? int n, err; ?
// ? ?printk("lk~~~~~~~read buff = %s\n",buff); ?
? ? err = copy_from_user(str, buff, count); ?
// ? ?printk("lk~~~~~~~read str = %s\n",str); ?
? ? if (err) ?
? ? ? ? return -EFAULT; ?
??
? ? sprintf(out,"return values"); ?
? ? memset(buff, 0, count); ?
? ? err = copy_to_user(buff, out, sizeof(out)); ?
? ? if (err) ?
? ? ? ? return -EFAULT; ??
??
? ? return n; ?
} ?
??
static ssize_t user_gpio_write(struct file *fp, const char __user *buff, ?
? ? ? ? ? ? ? ? ? ? ? ? size_t count, loff_t *offp) ?
{ ?
? ? int err; ?
? ? char tmp[32]; ?
? ? ?
// ? ?printk("lk~~~~~~~write buff = %s\n",buff); ?
? ? err = copy_from_user(tmp, buff, count); ?
// ? ?printk("lk~~~~~~~write tmp = %s\n",tmp); ?
??
? ? if (err) ?
? ? ? ? return -EFAULT; ?
? ? if('1' == tmp[0]) ?
? ? { ?
? ? ? ? //LINE IN ?
? ? ? ? printk("line in\n"); ?
? ? } ?
? ? else if('0' == tmp[0]) ?
? ? { ?
? ? ? ? //MIC IN ?
? ? ? ? printk("mic in\n"); ?
? ? } ?
??
? ? return count; ?
} ?
??
static ssize_t user_gpio_open(struct inode *inode,struct file *fp) ??
{ ?
// ?printk("open gpio devices\n"); ??
? ??
? return 0; ??
} ??
??
static struct file_operations user_gpio_file_ops = ??
{ ?
? .owner = THIS_MODULE, ?
? .write = user_gpio_write, ?
? .read = user_gpio_read, ?
? .open = user_gpio_open, ??
}; ?
??
static struct cdev *dev; ?
??
static void __exit user_audioIN_exit(void) ?
{ ?
? ? printk("exit audioIN\n"); ?
? ? dev_t devno = MKDEV(DEV_MAJOR, DEV_MINOR); ?
??
? ? unregister_chrdev_region(devno, NUM_MINORS); ?
??
? ? cdev_del(dev); ?
??
? ? return; ?
} ?
??
static int __init user_audioIN_init(void) ?
{ ?
? ? printk("init audioIN\n"); ?
? ? int err = 0; ?
? ? int i; ?
? ? dev_t devno = MKDEV(DEV_MAJOR, DEV_MINOR); ?
??
? ? err = register_chrdev_region(devno, NUM_MINORS, DEVICE_NAME); ?
??
? ? if (err) ?
? ? ? ? goto fail_devno; ?
??
? ? dev = cdev_alloc(); ?
? ? dev->ops = &user_gpio_file_ops; ?
? ? dev->owner = THIS_MODULE; ?
? ? ?
? ? err = cdev_add(dev, devno, NUM_MINORS); ?
??
? ? if (err) ?
? ? ? ? goto fail_cdev; ?
? ? ??
? ? return err; ?
fail_cdev: ?
fail_devno: ?
? ? unregister_chrdev_region(devno, NUM_MINORS); ?
fail_gpio: ?
? ? return err; ?
} ?
??
module_init(user_audioIN_init); ?
module_exit(user_audioIN_exit); ?
??
MODULE_LICENSE("GPL"); ?
MODULE_AUTHOR("kang_liu <liukang325@qq.com>"); ?
MODULE_DESCRIPTION("Access GSEIO from userspace."); ??


這里就可以調用kernel層的一些API進行底層的操作。


Makefile:生成audioINdriver.ko
[plain] view plain copy
# Comment/uncomment the following line to disable/enable debugging ?
#DEBUG = y ?
BUILD_TOOLS_PRE = arm-linux- ?
??
CC=$(BUILD_TOOLS_PRE)gcc ?
LD=$(BUILD_TOOLS_PRE)ld ?
# Add your debugging flag (or not) to CFLAGS ?
ifeq ($(DEBUG),y) ?
? DEBFLAGS = -O -g -DSCULL_DEBUG # "-O" is needed to expand inlines ?
else ?
? DEBFLAGS = -O2 ?
endif ?
??
KERNEL_DIR = ../../../linux-2.6.35.4 ?
??
EXTRA_CFLAGS += $(DEBFLAGS) ?
EXTRA_CFLAGS += -I$(LDDINC) ?
EXTRA_CFLAGS +=-I$(KERNEL_DIR)/arch/arm/mach-w55fa92/include ?
EXTRA_CFLAGS +=-I$(KERNEL_DIR)/arch/arm ?
EXTRA_CFLAGS +=-I$(KERNEL_DIR)/arch/arm/include ?
EXTRA_CFLAGS +=-I$(KERNEL_DIR)/arch/arm/include/linux ?
??
ifneq ($(KERNELRELEASE),) ?
# call from kernel build system ?
??
audioIN-objs := audioINdriver.o ?
??
obj-m ? := audioINdriver.o ?
??
else ?
KERNELDIR ?= $(KERNEL_DIR) ?
#KERNELDIR ?= /lib/modules/$(shell uname -r)/build ?
PWD ? ? ? := $(shell pwd) ?
??
modules: ?
? ? $(MAKE) ARCH=arm CROSS_COMPILE=$(BUILD_TOOLS_PRE) -C $(KERNELDIR) M=$(PWD) LDDINC=$(PWD)/../include modules ?
??
endif ?
??
clean: ?
? ? rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules.order Module.symvers ?
??
depend .depend dep: ?
? ? $(CC) $(CFLAGS) -M *.c > .depend ?
??
??
ifeq (.depend,$(wildcard .depend)) ?
include .depend ?
endif ?


3. 生成好 .ko 以后,就可以在ARM板上,加載驅動。
insmod audioINdriver.ko


4、加載驅動成功后,就可以在應用層直接操作設備 /dev/audioIN,來實現相關功能,將一些參數傳到驅動層,執行相關kernel層的代碼。
應用層測試程序如下:
[cpp] view plain copy
#include <stdio.h> ?
#include <stdlib.h> ?
#include <string.h> ?
#include <unistd.h> ?
#include <errno.h> ?
#include <fcntl.h> ?
#include <sys/types.h> ?
#include <sys/stat.h> ?
#include <sys/syscall.h> ?
#define BUF_LEN 32 ?
int s_audioInFd = 0; ?
??
int InitAudioInDevice() ?
{ ?
? ? s_audioInFd = open("/dev/audioIN",O_RDWR); ?
??
? ? if (s_audioInFd > 0) ?
? ? { ?
? ? ? ? return 1; ?
? ? } ?
? ? else ?
? ? { ?
? ? ? ? printf("Can't open the GSE IO device\n"); ?
? ? ? ? return 0; ?
? ? } ?
} ?
??
void UninitAudioInDevice() ?
{ ?
? ? if (s_audioInFd > 0) ?
? ? ? ? close(s_audioInFd); ?
} ?
??
int getAudioIn() ?
{ ?
? ? char buffer[BUF_LEN] = {0}; ?
? ? if (s_audioInFd > 0) ?
? ? { ?
? ? ? ? memcpy(&buffer[0], "lk_test", 7); ?
? ? ? ? // ? ? ?printf("get buffer = %s\n", buffer); ?
? ? ? ? int len = read(s_audioInFd, buffer, 7); ?
? ? ? ? // ? ? ?printf("get buffer = %s, len = %d\n", buffer, len); ?
? ? ? ? return len; ?
? ? } ?
??
? ? return -1; ?
} ?
??
int setAudioIn(int micLine) ?
{ ?
? ? char buffer[BUF_LEN] = {0}; ?
? ? if (s_audioInFd > 0) ?
? ? { ?
? ? ? ? sprintf(buffer, "%d", micLine); ?
? ? ? ? int len = write(s_audioInFd, buffer, sizeof(buffer)); ?
? ? ? ? if (len > 0) ?
? ? ? ? ? ? return 1; ?
? ? } ?
??
? ? return 0; ?
} ?


其中的read 和 write函數,可從驅動中獲取一些返回值,也可將字符串傳到驅動中。
驅動的入口為:
module_init(user_audioIN_init);
module_exit(user_audioIN_exit);
========

一個簡單的Linux字符型設備驅動和應用程序的例子



Linux內核支持可插入式模塊,驅動程序可以編譯在內核中,也可以編譯為內核的模塊文件。這點和應用程序差不多。常見的驅動程序是作為模塊動態加載的,比如聲卡,網卡等。而Linux最基本的驅動,如CPU,PCI總線,TCP/IP,APM,VFS等則直接編譯到內核文件中。


下面以一個簡單的字符型設備驅動為例:


./a.c


#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/errno.h>
#include <asm/segment.h>
#include <asm/uaccess.h>
#define DEVICE_NAME "qq_char"


unsigned int fs_major = 0;




static ssize_t test_read(struct file *file, char *buf, size_t count, loff_t *f_pos);
static int test_open(struct inode *node, struct file *file);
static int test_release(struct inode *inode, struct file *file);


static struct file_operations char_fops =?
{
read: test_read,
open: test_open,
release: test_release
};




static ssize_t test_read(struct file *file, char *buf, size_t count, loff_t *f_pos)
{
int len;
? ? ? ? for(len = count; len > 0; len--){
? ? ? ? ? ? put_user(1, buf); ? // 將數據放入用戶空間的buf中
? ? ? ? ? ? buf++;
? ? ? ?}
? ? ? ? return count;
}




// 打開設備
static int test_open(struct inode *node, struct file *file)
{
return 0;
}


// 釋放設備
static int test_release(struct inode *inode, struct file *file)
{
return 0;
}




// 注冊設備
int init_test(void)
{
int res;


res = register_chrdev(0, DEVICE_NAME, &char_fops);
if(res < 0){
? ?printk("can't get major name!\n");
? ?return res;
}


if(fs_major == 0)
? ?fs_major = res;


? ? ? ? printk("now, i am in kernel mode!\n");
return 0;
}


// 卸載設備
void cleanup_test(void)
{
? ? ? ? printk("now, i am out of kernel\n");
unregister_chrdev(fs_major, DEVICE_NAME); ? ??
}




module_init(init_test);
module_exit(cleanup_test);


編譯后,如果是2.6版本,則會生成a.ko
執行insmod a.ko后,內核調用module_init(),從而調用register_chrdev()函數注冊該設備。
如果注冊成功,則會在/proc/devices下面看到DEVICE_NAME,也就是qq_char,以及主設備號和次設備號,這就是注冊了的設備。
如果想卸載設備,則rmmod a,內核調用module_exit(),從而調用unregister_chrdev(),卸載掉該設備。


注冊完設備之后,因為/proc是個偽文件系統,/proc/devices下面的qq_char并不是真正的設備文件,所以還要將該設備映射為設備文件。
mknod /dev/qq_char c 253 0
生成設備文件后,可以在/dev下面看到qq_char。好了,到現在為止,我們的設備驅動已經做好了。




下面我們用一個簡單的應用程序來測試這個驅動。
./aa.c
#include <stdio.h>
#include <sys/types.h> // pid_t
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>




int main(void)
{
int testdev;
int i;


char buf[10];


testdev = open("/dev/qq_char", O_RDWR); ? // 打開設備qq_char


if(testdev == -1){
? ? printf("cann't open file \n");
? ? exit(0);
}
? ?
? ?printf("now read \n");


? ?read(testdev, buf, 10); ? // 從qq_char讀數據到buf中


? ?printf("read end \n");


? ?for(i = 0; i < 10; i++){
? ? ? ? printf("%d\n", buf);
? ?}


? ?close(testdev);


? ?return 0;
}


編譯,運行該程序,產生如下結果
now read?
read end?
1
1
1
1
1
1
1
1
1
1


======== 《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀

總結

以上是生活随笔為你收集整理的Linux字符驱动开发学习总结的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。