看了好長(zhǎng)一段時(shí)間的書(shū),開(kāi)始做驅(qū)動(dòng),從簡(jiǎn)單的開(kāi)始學(xué)習(xí),實(shí)現(xiàn)最簡(jiǎn)單的字符設(shè)備驅(qū)動(dòng),實(shí)現(xiàn)的功能是對(duì)兩塊內(nèi)存空間(物理內(nèi)存)進(jìn)行控制,實(shí)現(xiàn)讀寫(xiě)操作。
基本的實(shí)現(xiàn)與很多學(xué)習(xí)驅(qū)動(dòng)程序設(shè)計(jì)的方式一樣,跟著別人的程序跑,自己弄明白,搞清楚其中的道理。具體的實(shí)現(xiàn)過(guò)程,結(jié)合注釋可以看清楚。主要總結(jié)第一次寫(xiě)驅(qū)動(dòng)過(guò)程中存在的問(wèn)題,以及相應(yīng)的解決方法。
字符設(shè)備驅(qū)動(dòng)的基本流程:
首先,驅(qū)動(dòng)函數(shù)的初始化,以及清除函數(shù)(這部分主要是按著一定的模式編程)
1、 申請(qǐng)主設(shè)備號(hào),其中可以采用靜態(tài)或者動(dòng)態(tài)的方式申請(qǐng)。
2、 創(chuàng)建字符設(shè)備,其中包括初始化字符設(shè)備(分配設(shè)備空間),綁定相關(guān)的操作(相應(yīng)的操作),最后是添加字符設(shè)備(實(shí)現(xiàn)所申請(qǐng)的設(shè)備號(hào)與設(shè)備的綁定)。
3、 設(shè)備的具體實(shí)現(xiàn)以及錯(cuò)誤的處理問(wèn)題。
4、 清除函數(shù)主要包含分配物理空間的釋放以及設(shè)備號(hào)的釋放,字符設(shè)備的注銷(xiāo)等。
第二,主要是設(shè)備相關(guān)操作的具體實(shí)現(xiàn)過(guò)程。(這部分主要是涉及內(nèi)核的一些知識(shí))包含具體的文件打開(kāi),關(guān)閉,以及讀寫(xiě),定位操作。具體的參看源碼。
第三,Makefile的基本實(shí)現(xiàn)(也是一定的模塊,但是能夠了解其中的道理)或者直接編譯到內(nèi)核中。
出現(xiàn)的問(wèn)題主要是Makefile文檔的設(shè)計(jì)以及加載問(wèn)題。
問(wèn)題一,是kmalloc函數(shù)等的運(yùn)用需要相應(yīng)的頭文件,kmalloc的頭文件是linux/slab.h,關(guān)于錯(cuò)誤(errno.h)是與CPU體系密切相關(guān)的,因此通常是asm/errno.h,這個(gè)也是常見(jiàn)的問(wèn)題。
問(wèn)題二,是加載出錯(cuò)的問(wèn)題。出現(xiàn)下面的錯(cuò)誤,
insmod:error inserting '/home/gong/program/cprogram/module/memdev/memdev.ko': -1 Device or resource busy
這個(gè)錯(cuò)誤產(chǎn)生的原因主要是因?yàn)橹髟O(shè)備號(hào)真正被別的驅(qū)動(dòng)程序使用導(dǎo)致的。可以通過(guò)cat /proc/devices查看具體的情況
...
251 hidraw
252 usbmon
253 bsg
254 rtc
...
需要重新定義主設(shè)備號(hào)。然后重新編譯,即可加載成功。
問(wèn)題三,Makefile文件的設(shè)計(jì)
編譯驅(qū)動(dòng)有兩種方法,可以采用直接內(nèi)核編譯的方式,這種方式主要通過(guò)修改源碼中的Makefile和Kconfig實(shí)現(xiàn)。這種方法比較直觀,但是會(huì)破壞源碼的完整性。
通常采用自己編寫(xiě)Makefile的方式實(shí)現(xiàn)驅(qū)動(dòng)的編譯。這種方式需要了解Makefile的一些語(yǔ)法,同時(shí)需要了解自己的源碼結(jié)構(gòu),但是這種模式具有很大的相似性,都是基于模塊的設(shè)計(jì)方式。
常用的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
具體的分析我說(shuō)說(shuō)自己的理解吧。首先第一句ifneq ($(KERNELRELEASE),)是實(shí)現(xiàn)兩個(gè)變量的比較。比較兩個(gè)參量是否不相等。如果不相等則執(zhí)行下面的語(yǔ)句:obj-m := memdev.o,如果相等就執(zhí)行else后面的語(yǔ)句。
具體的過(guò)程如下:
如果驅(qū)動(dòng)代碼是在內(nèi)核源碼中實(shí)現(xiàn),那么KERNELRELEASE就是一個(gè)已知的值,具體的值是:
# 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)
但是另一個(gè)值為空,這樣兩個(gè)值就是不相等的,這樣就執(zhí)行obj-m := memdev.o。
但是如果不是在內(nèi)核源碼中實(shí)現(xiàn),那么KERNELRELEASE就是一個(gè)空值,這樣兩個(gè)參數(shù)就是相等的。執(zhí)行else以后的語(yǔ)句。
KDIR :=/opt/LinuxKernel/linux-2.6.38.1
PWD :=$(shell pwd)
上面的兩句是定義兩個(gè)變量,其中的KDIR是內(nèi)核源碼的根目錄,PWD則是當(dāng)前驅(qū)動(dòng)代碼所在的目錄。
然后執(zhí)行
default:
make -C $(KDIR) M=$(PWD) modules
default是一個(gè)默認(rèn)的目標(biāo),沒(méi)有相關(guān)的依賴(lài),規(guī)則是make -C $(KDIR) M=$(PWD) modules
其中參數(shù)-C和M=都有各自的意義,具體的可以參看Makefile的相關(guān)資料。
我的理解就是:
-C 選項(xiàng)的作用是指將當(dāng)前工作目錄轉(zhuǎn)移到你所指定的位置(頁(yè)就是內(nèi)核源碼的根目錄)
“M=dir”,內(nèi)核程序會(huì)自動(dòng)到你所指定的dir目錄中查找模塊源碼,將其編譯,生成.KO文件。實(shí)現(xiàn)驅(qū)動(dòng)程序的編譯。
具體的注意事項(xiàng):KERNELRELEASE這個(gè)變量不要寫(xiě)錯(cuò),這個(gè)比較容易出錯(cuò),還容易檢查出來(lái)。我第一次寫(xiě)代碼就出現(xiàn)了相應(yīng)的錯(cuò)誤。
需要注意的是該makefile被執(zhí)行了兩次,其中第一次是由于沒(méi)有定義KERNELRELEASE對(duì)象,當(dāng)進(jìn)入到內(nèi)核源碼以后,這時(shí)KERNELRELEASE對(duì)象已經(jīng)被定義好了,然后返回到當(dāng)前的文件夾下,這時(shí)候直接執(zhí)行第一句obj-m := memdev.o。這樣就實(shí)現(xiàn)了內(nèi)核的編譯。
具體的應(yīng)用程序代碼我都是按著別人的代碼修改調(diào)試的。
下面是我的源碼:
第一個(gè)代碼是頭文件:
#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
第二個(gè)是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>
/*該頭文件主要實(shí)現(xiàn)內(nèi)存的控制,包括kmalloc等函數(shù)*/
#include<linux/slab.h>
#include<asm/system.h>
#include<asm/io.h>
#include<asm/uaccess.h>
/*錯(cuò)誤的不同,需要具體的類(lèi)型*/
#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;
/*函數(shù)的聲明*/
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,
};
/*初始化函數(shù)*/
static int memdev_init(void)
{
int result;
int i;
/*創(chuàng)建一個(gè)設(shè)備號(hào)*/
dev_t devno = MKDEV(mem_major,0);
/*注冊(cè)一個(gè)設(shè)備號(hào)*/
/*如果定義了主設(shè)備號(hào)采用靜態(tài)申請(qǐng)的方式*/
if(mem_major)
result = register_chrdev_region(devno,2,"mem_dev");
else/*動(dòng)態(tài)申請(qǐng)?jiān)O(shè)備號(hào)*/
{
result = alloc_chrdev_region(&devno,0,2,"mem_dev");
mem_major = MAJOR(result);
}
/*錯(cuò)誤處理*/
if(result < 0)
return result;
/*創(chuàng)建一個(gè)設(shè)備*/
/*初始化cdev,并將相關(guān)的文件操作添加進(jìn)來(lái)*/
cdev_init(&cdev,&mem_fops);
cdev.owner = THIS_MODULE;
cdev.ops = &mem_fops;
/*注冊(cè)字符設(shè)備*/
cdev_add(&cdev,MKDEV(mem_major,0),MEMDEV_NR_DEVS);
/*分配兩個(gè)內(nèi)存空間,此處是在物理內(nèi)存上實(shí)現(xiàn)分配,實(shí)質(zhì)是創(chuàng)建兩個(gè)設(shè)備*/
mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev),GFP_KERNEL);
if(!mem_devp)/*出錯(cuò)的相應(yīng)操作*/
{
result = -ENOMEM;
/*錯(cuò)誤處理,采用典型的goto語(yǔ)句*/
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);
/*問(wèn)題,沒(méi)有進(jìn)行錯(cuò)誤的控制*/
memset(mem_devp[i].data,0,MEMDEV_SIZE);
}
return 0;
fail_malloc:
unregister_chrdev_region(devno,1);
return result;
}
/*模塊清除函數(shù)*/
static void memdev_exit(void)
{
cdev_del(&cdev);/*注銷(xiāo)字符設(shè)備*/
/*釋放兩個(gè)物理內(nèi)存*/
kfree(mem_devp[0].data);
kfree(mem_devp[1].data);
kfree(mem_devp);/*釋放設(shè)備結(jié)構(gòu)體內(nèi)存*/
unregister_chrdev_region(MKDEV(mem_major,0),2);
}
/*定義相關(guān)的操作函數(shù)*/
/*mem_open*/
int mem_open(struct inode *inode,struct file *filp)
{
struct mem_dev *dev;
/*判斷設(shè)備文件的次設(shè)備號(hào)*/
int num = MINOR(inode->i_rdev);
if(num >= MEMDEV_NR_DEVS)
return -ENODEV;
dev = &mem_devp[num];
/*將數(shù)據(jù)指向兩個(gè)內(nèi)存空間*/
filp->private_data = dev;
return 0;
}
/*release函數(shù)的實(shí)現(xiàn)*/
int mem_release(struct inode *inode, struct file *flip)
{
return 0;
}
/*read函數(shù)的實(shí)現(xiàn)*/
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結(jié)構(gòu)體上的指針*/
struct mem_dev *dev = filp->private_data;
/*參數(shù)的檢查*/
if(p >= MEMDEV_SIZE)
return 0;
if(count > MEMDEV_SIZE - p)
count = MEMDEV_SIZE - p;
/*從內(nèi)核讀數(shù)據(jù)到用戶(hù)空間*/
if(copy_to_user(buf,(void *)(dev->data + p),count))
{
/*出錯(cuò)誤*/
ret = -EFAULT;
}
else
{
/*移動(dòng)當(dāng)前文件光標(biāo)的位置*/
*ppos += count;
ret = count;
printk(KERN_INFO "read %d bytes(s) from %d\n",count,p);
}
return ret;
}
/*write函數(shù)的實(shí)現(xiàn)*/
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;
/*獲得設(shè)備結(jié)構(gòu)體的指針*/
struct mem_dev *dev = filp->private_data;
/*檢查參數(shù)的長(zhǎng)度*/
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的實(shí)現(xiàn)*/
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;
}
/*作者以及權(quán)限的聲明*/
MODULE_AUTHOR("GongPing");
MODULE_LICENSE("GPL");
/*通過(guò)宏module_init和module_exit實(shí)現(xiàn)模塊添加*/
module_init(memdev_init);
module_exit(memdev_exit);
第三個(gè)是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個(gè)是應(yīng)用程序:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
FILE *fp0 = NULL;
char Buf[4096];
/*復(fù)制數(shù)據(jù)到buf中*/
strcpy(Buf,"Mem is char devices!");
printf("Buf: %s\n",Buf);
/*打開(kāi)設(shè)備文件*/
fp0 = fopen("/dev/memdev0","rw");
/*錯(cuò)誤處理*/
if(fp0 == NULL)
{
printf("Open Memdev0 Error!\n");
return -1;
}
/*寫(xiě)數(shù)據(jù)到設(shè)備中*/
//fread(Buf,sizeof(Buf),1,fp0);
fwrite(Buf,sizeof(Buf),1,fp0);
/*定位*/
fseek(fp0,0,SEEK_SET);
/*復(fù)制數(shù)據(jù)到Buf*/
strcpy(Buf,"Buf is NULL!");
/*打印*/
printf("Buf: %s\n",Buf);
/*讀數(shù)據(jù)*/
fread(Buf,sizeof(Buf),1,fp0);
printf("BUF: %s\n",Buf);
fclose(fp0);
return 0;
}
源碼是別人的,我只是做了一下注釋?zhuān)缓笞隽诉m當(dāng)?shù)男薷模?qū)動(dòng)程序與應(yīng)用程序以及硬件代碼存在較大的差別,以后要多寫(xiě),多理解,多總結(jié)。