該文轉載自:http://rangercyh.blog.51cto.com/1444712/521244
系統調用是操作系統內核和應用程序之間的接口,而設備驅動程序是操作系統內核和機器硬件之間的接口。
設備驅動程序為應用程序屏蔽了硬件的細節,這樣在應用程序看來,硬件設備只是一個設備文件, 應用程序可以像操作普通文件一樣對硬件設備進行操作。
設備驅動程序是內核的一部分,它完成以下的功能:
(1) 對設備初始化和釋放.
(2) 把數據從內核傳送到硬件和從硬件讀取數據.
(3) 讀取應用程序傳送給設備文件的數據和回送應用程序請求的數據.
(4) 檢測和處理設備出現的錯誤.
Linux支持三中不同類型的設備:字符設備(character devices)、塊設備(block devices)和網絡設備(network interfaces)。
字符設備和塊設備的主要區別是:在對字符設備發出讀/寫請求時,實際的硬件I/O一般就緊接着發生了。
塊設備則不然,它利用一塊系統內存作緩沖區,當用戶進程對設備請求能滿足用戶的要求,就返回請求的數據,如果不能,就調用請求函數來進行實際的I/O操作。塊設備是主要針對磁盤等慢速設備設計的,以免耗費過多的CPU時間來等待.
用戶進程是通過設備文件來與實際的硬件打交道,每個設備文件都都有其文件屬性(c/b),表示是字符設備還是塊設備。
另外每個文件都有兩個設備號,第一個是主設備號,標識驅動程序,第二個是從設備號,標識使用同一個設備驅動程序的不同的硬件設備,
比如有兩個軟盤,就可以用從設備號來區分他們.設備文件的的主設備號必須與設備驅動程序在登記時申請的主設備號一致,否則用戶進程將無法訪問到驅動程序.。
設備驅動程序工作的基本原理:
用戶進程利用系統調用對設備進行諸如read/write操作,系統調用通過設備文件的主設備號找到相應的設備驅動程序,然后讀取這個數據結構相應的函數指針,接着把控制權交給該函數。
最后,在用戶進程調用驅動程序時,系統進入核心態,這時不再是搶先式調度。也就是說,系統必須在你的驅動程序的子函數返回后才能進行其他的工作。
如果你的驅動程序陷入死循環,你只有重新啟動機器了。
下面我們就來添加一個字符設備:
- 編寫設備驅動源代碼
在設備驅動程序中有一個非常重要的結構file_operations,該結構的每個域都對應着一個系統調用。
用戶進程利用系統調用對設備文件進行諸如read/write操作時,系統調用通過設備文件的主設備號找到相應的設備驅動程序,然后讀取這個數據結構相應的函數指針,接着把控制權交給該函數。
- struct file_operations {
- int (*seek) (struct inode * ,struct file *, off_t ,int);
- int (*read) (struct inode * ,struct file *, char ,int);
- int (*write) (struct inode * ,struct file *, off_t ,int);
- int (*readdir) (struct inode * ,struct file *, struct dirent * ,int);
- int (*select) (struct inode * ,struct file *, int ,select_table *);
- int (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long);
- int (*mmap) (struct inode * ,struct file *, struct vm_area_struct *);
- int (*open) (struct inode * ,struct file *);
- int (*release) (struct inode * ,struct file *);
- int (*fsync) (struct inode * ,struct file *);
- int (*fasync) (struct inode * ,struct file *,int);
- int (*check_media_change) (struct inode * ,struct file *);
- int (*revalidate) (dev_t dev);
- }
編寫設備驅動程序的主要工作是編寫子函數,並填充file_operations的各個域。
例如:
- Struct file_operations my_fops={
- .read=my_read,
- .write=my_write,
- .open=my_open,
- .release=my_release
- }
然后再定義函數my_read,my_write,my_open,my_release相應的函數體。
例如:
- static ssize_t my_open(struct inode *inode,struct file *file){
- static int counter=0;
- if(Device_Open)
- return -EBUSY;
- Device_Open++;
- /*寫入設備的信息*/
- sprintf(msg,"the device has been called %d times\n",counter++);
- msg_ptr=msg;
- return 0;
- }
同時對於可卸載的內核模塊(LKM),至少還有兩個基本的模塊:
例如本例中的:
- static int __init my_init(void){
- int result;
- result=register_chrdev(0,"sky_driver",&my_fops);
- if(result<0){
- printk("error:can not register the device\n");
- return -1;
- }
- if(my_major==0){
- my_major=result;
- printk("<1>hehe,the device has been registered!\n");
- printk("<1>the virtual device was assigned major number %d.\n",my_major);
- printk("<1>To talk to the driver,create a dev file with\n");
- printk("<1>'mknod/dev/my c %d 0'\n",my_major);
- printk("<1>Remove the dev and the file when done\n");
- }
- return 0;
- }
- static void __exit my_exit(void){
- unregister_chrdev(my_major,"sky_driver");
- printk("<1>unloading the device\n");
- }
my_init 用於注冊設備,獲得設備的主設備號
調用register_chrdev(0,“sky_driver(設備名)”,&my_fops);
my_exit 用於注銷設備
調用unregister_chrdev(my_major, “sky_driver(設備名)”);
然后在程序尾再調用這兩個函數
Module_init(my_init);
Module_exit(my_exit)
MODULE_LICENSE(“GPL”);
編寫自己的驅動程序源文件mydriver.c:
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/fs.h>
- #include <linux/init.h>
- #include <linux/uaccess.h>
- #if CONFIG_MODVERSIONS == 1
- #define MODVERSIONS
- #include <linux/version.h>
- #endif
- #define DEVICE_NUM 0 //隨機產生一個設備號
- static int device_num = 0; //用來保存創建成功后的設備號
- static char buffer[1024] = "mydriver"; //數據緩沖區
- static int open_nr = 0; //打開設備的進程數,用於內核的互斥
- //函數聲明
- static int mydriver_open(struct inode *inode, struct file *filp);
- static int mydriver_release(struct inode *inode, struct file* filp);
- static ssize_t mydriver_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos);
- static ssize_t mydriver_write(struct file *file, const char __user *buf, size_t count, loff_t *f_pos);
- //填充file_operations結構相關入口
- static struct file_operations mydriver_fops = {
- .read = mydriver_read,
- .write = mydriver_write,
- .open = mydriver_open,
- .release = mydriver_release,
- };
- //打開函數
- static int mydriver_open(struct inode *inode, struct file *filp)
- {
- printk("\nMain device is %d, and the slave device is %d\n", MAJOR(inode->i_rdev), MINOR(inode->i_rdev));
- if (open_nr == 0) {
- open_nr++;
- try_module_get(THIS_MODULE);
- return 0;
- }
- else {
- printk(KERN_ALERT "Another process open the char device.\n");//進程掛起
- return -1;
- }
- }
- //讀函數
- static ssize_t mydriver_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos)
- {
- //if (buf == NULL) return 0;
- if (copy_to_user(buf, buffer, sizeof(buffer))) //讀緩沖
- {
- return -1;
- }
- return sizeof(buffer);
- }
- //寫函數,將用戶的輸入字符串寫入
- static ssize_t mydriver_write(struct file *file, const char __user *buf, size_t count, loff_t *f_pos)
- {
- //if (buf == NULL) return 0;
- if (copy_from_user(buffer, buf, sizeof(buffer))) //寫緩沖
- {
- return -1;
- }
- return sizeof(buffer);
- }
- //釋放設備函數
- static int mydriver_release(struct inode *inode, struct file* filp)
- {
- open_nr--; //進程數減1
- printk("The device is released!\n");
- module_put(THIS_MODULE);
- return 0;
- }
- //注冊設備函數
- static int __init mydriver_init(void)
- {
- int result;
- printk(KERN_ALERT "Begin to init Char Device!"); //注冊設備
- //向系統的字符登記表登記一個字符設備
- result = register_chrdev(DEVICE_NUM, "mydriver", &mydriver_fops);
- if (result < 0) {
- printk(KERN_WARNING "mydriver: register failure\n");
- return -1;
- }
- else {
- printk("mydriver: register success!\n");
- device_num = result;
- return 0;
- }
- }
- //注銷設備函數
- static void __exit mydriver_exit(void)
- {
- printk(KERN_ALERT "Unloading...\n");
- unregister_chrdev(device_num, "mydriver"); //注銷設備
- printk("unregister success!\n");
- }
- //模塊宏定義
- module_init(mydriver_init);
- module_exit(mydriver_exit);
- MODULE_LICENSE("GPL");
- 編譯該設備驅動代碼
然后將設備驅動源文件復制到/usr/src/linux/drivers/misc下
修改misc目錄下的Makefile文件,只要在最后添加一句即可:obj-m +=mydriver.o。
在/usr/src/linux/drivers/misc路徑下執行命令:Make -C /usr/src/linux SUBDIRS=$PWD modules編譯成功將得到mydriver.ko文件。
可以在misc目錄下觀察得到了mydriver.ko文件。
繼續執行insmod ./mydriver.ko命令掛載內核中的模塊。
然后通過lsmod命令可以看到增加的設備模塊mydriver。
輸入cat /var/log/messages可以看到設備注冊成功。
此時進入/proc/devices文件會看到在字符設備中有250 mydriver。前面的是系統分配的主設備號,后面是設備注冊名。
進入在/dev路徑下,執行命令:
mknod /dev/mydriver c 250 0
第一個參數是新建設備文件的地址和名字。
第二個參數是指創建的是字符設備文件。
第三個參數是主設備號,第四個參數是從設備號,自己隨便取。
執行成功會在/dev/char中看到一個新的設備文件mydriver
至此設備添加成功。
- 編譯測試程序。
編寫測試代碼如下:
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <string.h>
- #include <stdio.h>
- #include <fcntl.h>
- int main(void)
- {
- int fd;
- char buf[1024];
- char get[1024];
- memset(get, 0, sizeof(get));
- memset(buf, 0, sizeof(buf));
- printf("please enter a string you want input to mydriver:\n");
- gets(get);
- fd = open("/dev/mydriver", O_RDWR, S_IRUSR|S_IWUSR);//打開設備
- if (fd > 0) {
- read(fd, &buf, sizeof(buf));
- printf("The message in mydriver now is: %s\n", buf);
- //將輸入寫入設備
- write(fd, &get, sizeof(get));
- //讀出設備的信息並打印
- read(fd, &buf, sizeof(buf));
- printf("The message changed to: %s\n", buf);
- sleep(1);
- }
- else {
- printf("OMG...");
- return -1;
- }
- close(fd);//釋放設備
- return 0;
- }
gcc -o mydriver_test mydriver_test.c
./mydriver_test
輸入任意字符串,驅動程序將字符串拷貝進新加入的設備,然后再讀取出來,設備中保留字符串信息,再次輸入將覆蓋原來的信息








