1、字符設備:是指只能一個字節一個字節讀寫的設備,不能隨機讀取設備內存中的某一數據,讀取數據需要按照先后順序。字符設備是面向流的設備,常見的字符設備有鼠標、鍵盤、串口、控制台和LED設備等。
2、塊設備:是指可以從設備的任意位置讀取一定長度數據的設備。塊設備包括硬盤、磁盤、U盤和SD卡等。
每一個字符設備或塊設備都在/dev目錄下對應一個設備文件。linux用戶程序通過設備文件(或稱設備節點)來使用驅動程序操作字符設備和塊設備。
主設備號和次設備號(二者一起為設備號):
一個字符設備或塊設備都有一個主設備號和一個次設備號。主設備號用來標識與設備文件相連的驅動程序,用來反映設備類型。次設備號被驅動程序用來辨別操作的是哪個設備,用來區分同類型的設備。
驅動程序原理圖: 
那么對於剛接觸驅動的我們來說如何快速編寫一個驅動程序呢?
最好也是最快的方法是參考內核源代碼中的demo。例如現在,我想編寫我們的第一個字符驅動程序,那么我們可以看看別人是怎么實現的,在內核driver目錄下找到led的驅動程序,參考別人是如何實現。還有就是廠家的參考demo。這是我們最快的學習方式。和STM32學習固件庫函數一樣的道理。
先寫出兩個函數模型,打開(open)和寫(write)函數:
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此時的驅動是有問題的,現在只是在測試學習驅動階段,可暫且不管,現在我屏幕沒有企鵝了,花屏~~~繼續往后學習!
