驅動程序分爲三部分:驅動設計模式(linux規定的)內核相關模塊 硬件知識 。設備文件存放目錄在 /dev下面node
字符驅動模塊(高):以字節爲最小訪問單位,經常實現open,read,writelinux
網絡驅動模塊:以塊爲最小訪問單位,通常最小塊512字節,能夠是512字節的整數倍,可是linux容許塊設備訪問任意字節。
設計模式
塊設備驅動模塊:網絡事務
緩存
模塊手動命令添加到內核,.ko文件。insmod 網絡
編譯進內核,app
1,vi Kconfig 函數
config HELLO (HELLO 模塊名字)測試
bool "hello driver" (menuconfig 菜單中出現的名字)
spa
保存,make menuconfig 就會出現新的選項 hello driver設計
2,vi Makefile
設備操做方式
設備號用來區分設備文件。分爲主設備號和次設備號
主設備號:字符設備文件與字符設備驅動鏈接
次設備號:因爲設備可能多個,能夠用次設備號,區分哪一個設備。
設備號關鍵字dev_t 爲unsigned int 32類型, 高12位主設備號,低20位爲次設備號
MAJOR(dev_t dev)能夠獲得主設備,MINOR(dev_t dev)獲得次設備
靜態申請
開發者本身申請 register_chrdev_region函數註冊設備號
優勢:簡單
缺點:容易重複衝突
動態申請
內核分配 alloc_chrdev_region函數分配
優勢:易於驅動推廣
缺點:沒法在安裝驅動前建立設備文件
註銷設備號 unregister_chrdev_region()釋放設備號
使用mknod手工建立
mknod filename type major minor
mknod serial0 c 100 0 ;建立名字爲 serial0 字符設備 主設備號100,次設備號0
自動建立
利用udev(mdev)來實現設備文件的自動建立,首先應保證支持udev(mdev),由busybox配置。在驅動初始
化代碼裏調用class_create爲該設備建立一個class,再爲每一個設備調用device_create建立對應的設備。
Struct File;表明一個打開的文件,系統每打開一個文件就關聯一個struct file,文件關閉時釋放
loff_t f_pos ;文件讀寫位置,文件讀寫指針位置是變化的
struct file_operations *f_op ;操做函數指針的集合,.參數:指針指向後面的函數 用於 用戶空間與驅動程序進行通訊
內核操做使用上面變量時,就會給.owner 的THIS_MODULE加1。引用計數,能夠經過lsmod 查看。只有模塊計數爲 0,才能夠卸載模塊
上面函數名字能夠隨便起;mem_read可是函數參數不能改,如
參數第一個是文件指針fd,第二個緩存,第三個是個數,第四個是文件位置
Struct Inode :文件記錄了設備信息,一個文件一個Inode
字符設備使用struct cdev描述
分配cdev空間,使用指針時須要 struct cdev *cdev_alloc(void)分配內存
初始化cdev。cdev_init(cdev,ops);
內核添加cdev。cdev_add(設備結構,設備號,設備個數),設備文件與設備號鏈接起來了
設備註銷 cdev_del(struct cdev *p);
內核與用戶空間指針進行傳值的專門函數copy_from_user(),copy_to_user(),他們之間不能直接傳值
例子
/*********************************************/ #ifndef _MEMDEV_H_ #define _MEMDEV_H_ #ifndef MEMDEV_MAJOR #define MEMDEV_MAJOR 190 /*預設的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_ */ /*********************************************/
dev.c
#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 <linux/slab.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);// 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)//buf緩存區,size讀取文件大小,ppos當前讀寫位置 { unsigned long p = *ppos;//p爲當前讀寫位置 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;//count大於可讀取的範圍,則縮小讀取範圍。 /*讀數據到用戶空間*/ if (copy_to_user(buf, (void*)(dev->data + p), count))//返回buf,讀取位置,讀取數量 { 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)//write和read相似,直接參考read { 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)//做改變文件中的當前讀/寫位置, 而且新位置做爲(正的)返回值在測試程序中要從新定位文件位置,whence這裏被設置爲 SEEK_SET { 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;//從文件尾開始定位,因爲是從0開始,因此要減1 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);//MKDEV是將主設備號和次設備號轉換爲dev_t類型數據,參數mem_major在頭文件中預設爲254 /* 靜態申請設備號*/ if (mem_major)//memdev.h 中定義了爲254。因此本例爲靜態分配主設備號254 result = register_chrdev_region(devno, 2, "memdev");//devno爲主設備號,共申請兩個連續的設備,設備名爲"memdev" else /* 動態分配設備號 */ { result = alloc_chrdev_region(&devno, 0, 2, "memdev");//&devno做爲一個輸出參數,次設備號從0開始分配,申請2個設備,設備名爲"memdev" mem_major = MAJOR(devno);//獲取動態分配到的主設備號。 } if (result < 0)//result返回0時爲申請成功,反加負值爲申請失敗。 return result; /*初始化cdev結構*/ cdev_init(&cdev, &mem_fops);//初始化cdev結構,將結構體cdev和mem_fops綁定起來 cdev.owner = THIS_MODULE;//驅動引用計數,做用是這個驅動正在使用的時候,你再次用inmod命令時,出現警告提示 cdev.ops = &mem_fops; /* 註冊字符設備 */ cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);//MEMDEV_NR_DEVS=2,分配2個設備 /* 爲設備描述結構分配內存*/ mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);//kmalloc函數返回的是虛擬地址(線性地址). if (!mem_devp) /*申請失敗*/ { result = - ENOMEM; goto fail_malloc; } memset(mem_devp, 0, MEMDEV_NR_DEVS * sizeof(struct mem_dev));//新申請的內存作初始化工做 /*爲設備分配內存*/ for (i=0; i < MEMDEV_NR_DEVS; i++) { mem_devp[i].size = MEMDEV_SIZE;//#define MEMDEV_SIZE 4096 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("cicue"); MODULE_LICENSE("GPL"); module_init(memdev_init); module_exit(memdev_exit);
Makefile
ifneq ($(KERNELRELEASE),) obj-m := memdev.o else KDIR := /forlinux/linux-3.0.1 all: make -C $(KDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-linux- clean: rm -f *.ko *.o *.mod.o *.mod.c *.symvers endif
測試程序
#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);//調用mem_llseek()定位 /*清除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,將編譯的.ko和app都下載到開發板
2,加載模塊.ko insmod memdev.ko
3,建立設備文件 手動建立過程 (動態分配時:設備號查看 cat /proc/devices 安裝設備就會在該目錄下出現相應的設備和設備號)
cd /dev
mknod memdev0 c 190 0 (設備號要對應起來 運行這個後 會在/dev目錄下建立相應的文件)
4,讓後去運行app文件 ./memapp