看了好長一段時間的書,開始做驅動,從簡單的開始學習,實現最簡單的字符設備驅動,實現的功能是對兩塊內存空間(物理內存)進行控制,實現讀寫操作。
基本的實現與很多學習驅動程序設計的方式一樣,跟著別人的程序跑,自己弄明白,搞清楚其中的道理。具體的實現過程,結合注釋可以看清楚。主要總結第一次寫驅動過程中存在的問題,以及相應的解決方法。
字符設備驅動的基本流程:
首先,驅動函數的初始化,以及清除函數(這部分主要是按著一定的模式編程)
1、 申請主設備號,其中可以采用靜態或者動態的方式申請。
2、 創建字符設備,其中包括初始化字符設備(分配設備空間),綁定相關的操作(相應的操作),最后是添加字符設備(實現所申請的設備號與設備的綁定)。
3、 設備的具體實現以及錯誤的處理問題。
4、 清除函數主要包含分配物理空間的釋放以及設備號的釋放,字符設備的注銷等。
第二,主要是設備相關操作的具體實現過程。(這部分主要是涉及內核的一些知識)包含具體的文件打開,關閉,以及讀寫,定位操作。具體的參看源碼。
第三,Makefile的基本實現(也是一定的模塊,但是能夠了解其中的道理)或者直接編譯到內核中。
出現的問題主要是Makefile文檔的設計以及加載問題。
問題一,是kmalloc函數等的運用需要相應的頭文件,kmalloc的頭文件是linux/slab.h,關于錯誤(errno.h)是與CPU體系密切相關的,因此通常是asm/errno.h,這個也是常見的問題。
問題二,是加載出錯的問題。出現下面的錯誤,
insmod:error inserting '/home/gong/program/cprogram/module/memdev/memdev.ko': -1 Device or resource busy
這個錯誤產生的原因主要是因為主設備號真正被別的驅動程序使用導致的。可以通過cat /proc/devices查看具體的情況
...
251 hidraw
252 usbmon
253 bsg
254 rtc
...
需要重新定義主設備號。然后重新編譯,即可加載成功。
問題三,Makefile文件的設計
編譯驅動有兩種方法,可以采用直接內核編譯的方式,這種方式主要通過修改源碼中的Makefile和Kconfig實現。這種方法比較直觀,但是會破壞源碼的完整性。
通常采用自己編寫Makefile的方式實現驅動的編譯。這種方式需要了解Makefile的一些語法,同時需要了解自己的源碼結構,但是這種模式具有很大的相似性,都是基于模塊的設計方式。
常用的Makefile模塊如下所示:
ifneq ($(KERNELRELEASE),)
obj-m := memdev.o
else
KDIR :=/opt/LinuxKernel/linux-2.6.38.1
PWD :=$(shell pwd)
default:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -f *.ko *.o *.mod.o *.mod.c *.symvers
endif
具體的分析我說說自己的理解吧。首先第一句ifneq ($(KERNELRELEASE),)是實現兩個變量的比較。比較兩個參量是否不相等。如果不相等則執行下面的語句:obj-m := memdev.o,如果相等就執行else后面的語句。
具體的過程如下:
如果驅動代碼是在內核源碼中實現,那么KERNELRELEASE就是一個已知的值,具體的值是:
# Read KERNELRELEASE from include/config/kernel.release (if it exists)
KERNELRELEASE = $(shell cat include/config/kernel.release 2> /dev/null)
KERNELVERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
但是另一個值為空,這樣兩個值就是不相等的,這樣就執行obj-m := memdev.o。
但是如果不是在內核源碼中實現,那么KERNELRELEASE就是一個空值,這樣兩個參數就是相等的。執行else以后的語句。
KDIR :=/opt/LinuxKernel/linux-2.6.38.1
PWD :=$(shell pwd)
上面的兩句是定義兩個變量,其中的KDIR是內核源碼的根目錄,PWD則是當前驅動代碼所在的目錄。
然后執行
default:
make -C $(KDIR) M=$(PWD) modules
default是一個默認的目標,沒有相關的依賴,規則是make -C $(KDIR) M=$(PWD) modules
其中參數-C和M=都有各自的意義,具體的可以參看Makefile的相關資料。
我的理解就是:
-C 選項的作用是指將當前工作目錄轉移到你所指定的位置(頁就是內核源碼的根目錄)
“M=dir”,內核程序會自動到你所指定的dir目錄中查找模塊源碼,將其編譯,生成.KO文件。實現驅動程序的編譯。
具體的注意事項:KERNELRELEASE這個變量不要寫錯,這個比較容易出錯,還容易檢查出來。我第一次寫代碼就出現了相應的錯誤。
需要注意的是該makefile被執行了兩次,其中第一次是由于沒有定義KERNELRELEASE對象,當進入到內核源碼以后,這時KERNELRELEASE對象已經被定義好了,然后返回到當前的文件夾下,這時候直接執行第一句obj-m := memdev.o。這樣就實現了內核的編譯。
具體的應用程序代碼我都是按著別人的代碼修改調試的。
下面是我的源碼:
第一個代碼是頭文件:
#ifndef _MEMDEV_H_H_
#define _MEMDEV_H_H_
#ifndef MEMDEV_MAJOR
#define MEMDEV_MAJOR 555
#endif
#ifndef MEMDEV_NR_DEVS
#define MEMDEV_NR_DEVS 2
#endif
#ifndef MEMDEV_SIZE
#define MEMDEV_SIZE 2048
#endif
struct mem_dev
{
char *data;
unsigned long size;
};
#endif
第二個是C文件:
#include<linux/module.h>
#include<linux/types.h>
#include<linux/fs.h>
//#include<linux/errno.h>
#include<linux/sched.h>
#include<linux/init.h>
#include<linux/cdev.h>
/*該頭文件主要實現內存的控制,包括kmalloc等函數*/
#include<linux/slab.h>
#include<asm/system.h>
#include<asm/io.h>
#include<asm/uaccess.h>
/*錯誤的不同,需要具體的類型*/
#include<asm/errno.h>
/*自己定義的頭文件*/
#include"memdev.h"
static mem_major = MEMDEV_MAJOR;
module_param(mem_major,int,S_IRUGO);
module_param(mem_major,int,S_IRUGO);
struct mem_dev *mem_devp;
struct cdev cdev;
/*函數的聲明*/
static int memdev_init(void);
static void memdev_exit(void);
int mem_open(struct inode *inode,struct file *filp);
int mem_release(struct inode *inode, struct file *flip);
static ssize_t mem_read(struct file *flip,char __user *buf, size_t size,loff_t *ppos);
static ssize_t mem_write(struct file *filp,const char __user *buf,size_t size,loff_t *ppos);
static loff_t mem_llseek(struct file *filp,loff_t offset, int whence);
/*添加該模塊的基本文件操作支持*/
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,"mem_dev");
else/*動態申請設備號*/
{
result = alloc_chrdev_region(&devno,0,2,"mem_dev");
mem_major = MAJOR(result);
}
/*錯誤處理*/
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語句*/
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[0].data);
kfree(mem_devp[1].data);
kfree(mem_devp);/*釋放設備結構體內存*/
unregister_chrdev_region(MKDEV(mem_major,0),2);
}
/*定義相關的操作函數*/
/*mem_open*/
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;
}
/*release函數的實現*/
int mem_release(struct inode *inode, struct file *flip)
{
return 0;
}
/*read函數的實現*/
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;
/*獲得file結構體上的指針*/
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;
}
/*write函數的實現*/
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 "writted %d bytes(s) from %d\n",count,p);
}
return ret;
}
/*lseek的實現*/
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:
return -EINVAL;
}
filp->f_pos = newpos;
return newpos;
}
/*作者以及權限的聲明*/
MODULE_AUTHOR("GongPing");
MODULE_LICENSE("GPL");
/*通過宏module_init和module_exit實現模塊添加*/
module_init(memdev_init);
module_exit(memdev_exit);
第三個是Makefile:
ifneq ($(KERNELRELEASE),)
obj-m := memdev.o
else
#KDIR :=/usr/src/kernels/2.6.35.14-96.fc14.i686
#KDIR :=/lib/modules/2.6.35.14-96.fc14.i686/build
#KDIR ?=/opt/LinuxKernel/linux-2.6.38.1
KDIR :=/opt/LinuxKernel/linux-2.6.38.1
PWD :=$(shell pwd)
default:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -f *.ko *.o *.mod.o *.mod.c *.symvers
endif
第4個是應用程序:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
FILE *fp0 = NULL;
char Buf[4096];
/*復制數據到buf中*/
strcpy(Buf,"Mem is char devices!");
printf("Buf: %s\n",Buf);
/*打開設備文件*/
fp0 = fopen("/dev/memdev0","rw");
/*錯誤處理*/
if(fp0 == NULL)
{
printf("Open Memdev0 Error!\n");
return -1;
}
/*寫數據到設備中*/
//fread(Buf,sizeof(Buf),1,fp0);
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);
fclose(fp0);
return 0;
}
源碼是別人的,我只是做了一下注釋,然后做了適當的修改,驅動程序與應用程序以及硬件代碼存在較大的差別,以后要多寫,多理解,多總結。