一、字符設備:是指只能一個字節一個字節讀寫的設備,不能隨機讀取設備內存中的某一數據,讀取數據須要按照前後順序。字符設備是面向流的設備,常見的字符設備有鼠標、鍵盤、串口、控制檯和LED設備等。node
二、塊設備:是指能夠從設備的任意位置讀取必定長度數據的設備。塊設備包括硬盤、磁盤、U盤和SD卡等。linux
每個字符設備或塊設備都在/dev目錄下對應一個設備文件。linux用戶程序經過設備文件(或稱設備節點)來使用驅動程序操做字符設備和塊設備。shell
主設備號和次設備號(兩者一塊兒爲設備號):
一個字符設備或塊設備都有一個主設備號和一個次設備號。主設備號用來標識與設備文件相連的驅動程序,用來反映設備類型。次設備號被驅動程序用來辨別操做的是哪一個設備,用來區分同類型的設備。ubuntu
驅動程序原理圖:
網絡
那麼對於剛接觸驅動的咱們來講如何快速編寫一個驅動程序呢?app
最好也是最快的方法是參考內核源代碼中的demo。例如如今,我想編寫咱們的第一個字符驅動程序,那麼咱們能夠看看別人是怎麼實現的,在內核driver目錄下找到led的驅動程序,參考別人是如何實現。還有就是廠家的參考demo。這是咱們最快的學習方式。和STM32學習固件庫函數同樣的道理。async
先寫出兩個函數模型,打開(open)和寫(write)函數:ide
static int first_drv_open(struct inode *inode, struct file *file) { return 0; } static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { return 0; }
而後是要告訴內核有這兩個函數,怎樣告訴內核呢?經過定義下面這樣結構:函數
/* * NOTE: * read, write, poll, fsync, readv, writev, unlocked_ioctl and compat_ioctl * can be called without the big kernel lock held in all filesystems. */ 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 (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *); 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); }
而後經過一個函數告訴內核:學習
那麼誰來調用上面這個函數呢?調用上面這個函數的函數就叫作驅動入口函數(這裏是first_drv_init):
入口函數須要區分是哪一個驅動,因此須要修飾一下,怎麼修飾呢?就是調用一個函數:
完整的myled.c函數以下:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <asm/uaccess.h> #include <asm/irq.h> #include <asm/io.h> #include <asm/arch/regs-gpio.h> #include <asm/hardware.h> static int first_drv_open(struct inode *inode, struct file *file) { printk("first_drv_open...\r\n"); return 0; } static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { printk("first_drv_write...\r\n"); return 0; } /* 這個結構是字符設備驅動程序的核心 * 當應用程序操做設備文件時所調用的open、read、write等函數, * 最終會調用這個結構中指定的對應函數 */ static struct file_operations first_drv_fops = { .owner = THIS_MODULE, /* 這是一個宏,推向編譯模塊時自動建立的__this_module變量 */ .open = first_drv_open, .write = first_drv_write, }; int fisrt_drv_init(void) { register_chrdev(111, "first_drv", &first_drv_fops); return 0; } void fisrt_drv_exit(void) { unregister_chrdev(111, "first_drv"); } module_init(fisrt_drv_init); module_exit(fisrt_drv_init); MODULE_AUTHOR("http://www.100ask.net"); MODULE_VERSION("0.1.0"); MODULE_DESCRIPTION("S3C2410/S3C2440 LED Driver"); MODULE_LICENSE("GPL");
/*
更正上面一個錯誤,因爲粗心致使以後的程序出現bug:
*/
Makefile以下:
1 KERN_DIR =/home/book/Documents/linux-2.6.22.6 2 PWD := $(shell pwd) 3 all: 4 make -C $(KERN_DIR) M=$(PWD) modules 5 6 clean: 7 make -C $(KERN_DIR) M=$(PWD) modules clean 8 rm -rf modules.order 9 10 obj-m += myled.o
上面的Makefile通過了一次更改,以前韋老師的Makefile以下:
關鍵在於使用韋老師的`pwd`這個方式,我在ubuntu 16.04上make會失敗,查詢網上資料,改爲$(PWD)以後,終於make成功了。特別注意一點,在make驅動函數以前,須要先構建內核樹,其實就是保證在make驅動函數以前,先make一下內核。還有一點須要注意,想要加載驅動,在第一次make內核以後,把這次生成的uImage下載進入flash(若是沒法生成uImage,執行sudo apt-get install u-boot-tools便可),而後才能夠看到驅動被加載。還有就是,在使用不更改uboot參數的網絡文件系統,即經過手動mount的方式,這種狀況下insmod驅動的.ko文件,會比較耗時,甚至容易出現失敗或者長時間卡死狀態,因此建議選用set uboot參數的方式,這樣insmod的時候能夠快速響應:
如今寫個main函數測試這個驅動:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> int main(int argc, char **argv) { int fd; int val=1; fd=open("/dev/xxx",O_RDWR); if(fd<0) printf("can't open!\r\n"); write(fd,&val,4); return 0; }
在nfs共享目錄下編譯一下這個源文件:
生成可執行文件以後,在開發板上運行:
首先執行的時候,顯示不能打開,由於咱們尚未建立這樣的設備,使用mknod建立設備節點以後,能夠看到應用程序的open和write會觸發咱們驅動函數的open和write,證實咱們的入門測試成功了。建立設備採用/dev/xxx是爲了展現這個設備的名字,其實可有可無,可是最好能有意義。
固然,這裏只是咱們第一個測試程序,存在不足,咱們在驅動函數中是寫死了主設備號爲111,並且還須要手動建立節點,在以後的隨筆中,將對其進行改進。
(如今我是使用的通過uboot更改了參數的nfs網絡文件系統,這樣的方式insmod更快)
可是,如今有個問題,LCD此時的驅動是有問題的,如今只是在測試學習驅動階段,可暫且無論,如今我屏幕沒有企鵝了,花屏~~~繼續日後學習!