博主新開了個人站點,你也可以在這看到這篇文章,點擊打開鏈接
1、實驗目的:掌握簡單字符設備驅動設計規範模式,設備節點創建方法,應用程序的設計和編寫方法。
2、實驗要求:
(A.)在S3C2440(以tq2440和mini2440爲平臺驗證的)平臺上編寫實現了讀,寫,定位的字符設備驅動程序
(B.)編寫應用程序,對所寫的驅動程序進行測試
3、實驗步驟:
(A.)創建實驗目錄,用mkdir命令來創建
#mkdir /opt/FrinedlyARM/studydriver/5-1-1
#cd /opt/FrinedlyARM/stduydriver/5-1-1
4、在實驗目錄寫編寫實現了讀,寫,定位的字符設備驅動程序memdev.c
(溫馨提示:本實驗並沒有真正的去操控硬件設備,而是使用內存來模擬字符設備)
5、編寫Makefile
ifeq ($(KERNELRELEASE),)
KERNELDIR ?=/opt/FriendlyARM/linux-2.6.32.2(按照你的linux-2.6.32.2內核實際的目錄來改正)
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.markers *.mod.c *.mod.o *.symvers .tmp_versions
.PHONY: modules modules_install clean
else
obj-m := memdev.o
endif
編譯內核模塊並拷貝內核模塊到根文件系統(說明:memdev.ko爲編譯生成的內核模塊)
實驗源碼mem_dev.c
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include "memdev.h"
static mem_major = MEMDEV_MAJOR;
module_param(mem_major, int, S_IRUGO);
struct mem_dev *mem_devp; /*設備結構體指針*/
struct cdev cdev;
/*文件打開函數*/
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;
}
/*文件釋放函數*/
int mem_release(struct inode *inode, struct file *filp)
{
return 0;
}
/*讀函數*/
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;
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;
}
/*寫函數*/
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 "written %d bytes(s) from %d\n", count, p);
}
return ret;
}
/* seek文件定位函數 */
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: /* can't happen */
return -EINVAL;
}
if ((newpos<0) || (newpos>MEMDEV_SIZE))
return -EINVAL;
filp->f_pos = newpos;
return newpos;
}
/*文件操作結構體*/
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, "memdev");
else /* 動態分配設備號 */
{
result = alloc_chrdev_region(&devno, 0, 2, "memdev");
mem_major = MAJOR(devno);
}
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 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); /*釋放設備結構體內存*/
unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*釋放設備號*/
}
MODULE_AUTHOR("KPBoy huang");
MODULE_LICENSE("GPL");
module_init(memdev_init);
module_exit(memdev_exit);
代碼相關理論知識說明:
《1》確定主設備號和次設備號
(1)主設備號:是內核識別一個設備屬於哪一個驅動的標識。是一個整數,範圍爲0-(4096-1),但是一般使用1~255。
次設備號:是驅動程序自己用來區別多個設備的。是一個整數,範圍爲0~(1048576-1),但是一般使用1~255。
預定義的設備號可以參考內核源碼Documentation/devices.txt
(2)設備編號的內部表示。
內核用32bit表示設備號:
typedef unsigned long dev_t;
‚其中高12bit爲主設備號,低20bit爲次設備號。
要想獲得一個dev_t類型的變量中包含的主或者次設備號,使用內核定義的宏:MAJOR(dev_t dev) ;和MINOR(dev_t dev);
ƒ分配主設備號、次設備號的方法和內核API
int register_chrdev_region(dev_t first,unsigned int count ,char * name);
靜態申請設備號:請求操作系統分配驅動程序要求的特定設備號。first 爲要求分配的第一個設備號(包含主、次設備號),count爲請求的設備號數量,name爲驅動名稱(出現在/proc/devices中),失敗返回負數,成功則向操作系統將first到first+conut-1,總共count個設備號分配給驅動。例如:
如果int result = register_chrdev_region(devno, 2, "memdev")成功,則分配到第一個設備號爲devno,2爲請求的設備號數量,memdev爲驅動的名稱,失敗就返回負數。
動態申請設備號: result = alloc_chrdev_region(&devno, 0, 2, "memdev");devno用於存放結果,其最終存放的是分配到的2個設備號中的第一個設備號,firstminor即0爲期望分配到的第一個次設備號,name即爲memdev爲驅動的名稱(出現在/proc/devices中),失敗將返回負數,成功則操作系統將分配的第一個設備號存放到dev中,並將分配出去的設備號是從devno到devno + count - 1,共count個(在本例也就是2個),申請的時機應該在驅動程序的初始化函數中(本例的memdev_init(void)函數中)。
m釋放主設備號、次設備號的方法和內核API
釋放的時機應該在驅動程序的銷燬函數中。
unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*釋放設備號*/
《2》確定設備文件名程並創建設備文件作爲用戶程序和驅動的接口界面
①設備文件名稱是一個合法的文件名稱即可。一般是“設備名稱”,或者是“設備名稱 + 數字” (本例中是mem_dev)
②設備類型主要有c(字符設備類型),b(塊設備類型)
③創建設備文件 mknod /dev/memdev0 c 251 0(使用命令mknod 來創建設備文件)
《3》將字符設備註冊進操作系統
①字符設備的註冊時機是在驅動程序的初始化函數中,註銷的時機是在驅動程序的銷燬函數中:
/* 爲設備描述結構分配內存*/
mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
(相當於應用程序中的malloc)
memset(mem_devp, 0, sizeof(struct mem_dev));
②字符設備是如何在操作系統中被註冊和註銷的?(參看LDD3 3.1和3.6節)
詳細解析參閱《深入淺出嵌入式底層軟件開發》P428-429的8.2.2實現字符設備驅動的工作
Mem_dev.h實驗源碼
#ifndef _MEMDEV_H_
#define _MEMDEV_H_
#ifndef MEMDEV_MAJOR
#define MEMDEV_MAJOR 251 /*預設的mem的主設備號*/
#endif
#ifndef MEMDEV_NR_DEVS
#define MEMDEV_NR_DEVS 2 /*設備數*/
#endif
#ifndef MEMDEV_SIZE
#define MEMDEV_SIZE 4096
#endif
/*mem設備描述結構體*/
struct mem_dev
{
char *data;
unsigned long size;
};
#endif /* _MEMDEV_H_ */
6、通過NFS方式起根文件系統
7、加載內核模塊
#insmod memdev.ko
#lsmod
說明:在加載內核模塊時,模塊初始化函數memdev_init被調用,函數完成了設備號申請,字符設備註冊等操作。
8、查看設備名字和設備號
#cat /proc/devices
9、手工創建設備節點
#mknod /dev/memdev0 c 251 0(用命令mknod創建設備文件)
(A)、/dev/memdev0 ==》 設備文件名字爲“memdev0”,當然這裏的名字是可以修改的
(B)、c ==》 設備類型,c:爲字符設備,b,塊設備
(C)、251 ==》 主設備號
(D)、0 ==》 次設備號
(E)[root@FriendlyARM 2.6.32.2-FriendlyARM]# ls -l /dev/memdev0
crw-r--r-- 1 root root 251, 0 Feb 8 15:23 /dev/memdev0
11、編寫應用程序
用於測試驅動的應用程序app-mem.c
#include <stdio.h>
int main()
{
FILE *fp0 = NULL;
char Buf[4096];
/*初始化Buf*/
strcpy(Buf,"Mem is char dev!");
printf("BUF: %s\n",Buf);
/*打開設備文件*/
fp0 = fopen("/dev/memdev0","r+");
if (fp0 == NULL)
{
printf("Open Memdev0 Error!\n");
return -1;
}
/*寫入設備*/
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);
return 0;
}
實驗解析:
當應用程序調用open打開一個設備時,操作系統做了什麼?
由於操作系統內部已經建立了“設備號—cdev— mem_fops” 三者之間的關聯關係,所以當用戶程序調用 fp0 = fopen("/dev/memdev0","r+")打開設備文件的時候。操作系統就可以根據設備文件名得到設備號,再根據設備號找到cedv,進而找到fops,從而爲該設備在內核空間中建立3張表:文件描述符(file descriptor table),文件表(file table),i節點表(i-node table),關於3張表的關係和作用,請參看LDD3的3.3節
i節點表中含有:
①、i_rdev:字段代表實際的設備號(open調用中設備文件對應的設備號)
②、i_cdev:字段指向字符設備cdev.
文件表中含有:
①f_op :字段指向fops.
②f_ops:字段表示設備當前讀寫位置。
③f_flags:字段標識文件打開是可讀或可寫?
④private_data:字段指向私有數據指針,驅動程序可以將這個成員用於任何目的或者忽視這個成員。
《4》當應用程序調用fread(Buf, sizeof(Buf), 1, fp0)讀出設備時,操作系統做了什麼?
總結:所以我們寫驅動程序很大一部分工作就是要實現這些直接操作設備硬件的函數。
strcpy(拷貝字符串) |
|||
相關函數 |
bcopy,memcpy,memccpy,memmove |
||
表頭文件 |
#include<string.h> |
||
定義函數 |
char *strcpy(char *dest,const char *src); |
||
函數說明 |
strcpy()會將參數src字符串拷貝至參數dest所指的地址。 |
||
返回值 |
返回參數dest的字符串起始地址。 |
||
附加說明 |
如果參數dest所指的內存空間不夠大,可能會造成緩衝溢出(buffer Overflow)的錯誤情況,在編寫程序時請特別留意,或者用strncpy()來取代。 |
||
範例 |
|||
#include<string.h> main() { char a[30]=”string(1)”; char b[]=”string(2)”; printf(“before strcpy() :%s\n”,a); printf(“after strcpy() :%s\n”,strcpy(a,b)); } |
|||
執行 |
|||
before strcpy() :string(1) after strcpy() :string(2) |
fwrite函數
#include <stdio.h>
size_t fwrite( const void * ptr ,size_t size ,size_t nitems,FILE *stream);
fwrite庫函數用於往一個文件流裏面寫入數據,數據從文件流stream寫到ptr指向的數據緩衝區裏,fread和fwrite都是對數據記錄進行操作,size參數指定每個數據記錄的長度,計數器nitems給出要傳輸的記錄個數。它的返回值是成功讀到數據緩衝區的記錄個數(而不是字節數)。當到達文件尾時,他的返回值可能會小於nitems,甚至可以是零。
在本例中的fwrite(Buf, sizeof(Buf), 1, fp0)函數就是將fp0數據流裏面的數據寫入到Buf緩衝區裏面,sizeof(Buf)用來指定每個數據記錄的長度,“1”是計數器給出要傳輸的記錄個數
fseek函數
#include<stdio.h>
int fseek (FILE * stream , long int offset ,int whence );
fseek函數是與lseek系統調用對應的文件流函數。它在文件流裏面爲下一次讀寫操作指定位置。offset和whence參數的定義和取值和lseek系統調用完全一樣。fseek成功調用返回一個整數:0代表成功,-1代表失敗並設置errno指出的錯誤。
SEEK_SET:offset是一個絕對位置
SEEK_CUR:offset是相對於當前位置的一個相對位置。
SEEK_END:offset是相對於文件尾的一個相對位置。
12、交叉編譯應用程序並拷貝到根文件系統
#arm-linux-gcc -static app-mem.c -o app-mem
注意:由於我們製作的是靜態鏈接的根文件系統,故要添加編譯選項“-static”
13、運行測試程序
#./app-mem
(說明:應用程序首先打開設備節點,設備方法mem_open被調用;然後寫入數據到設備中,設備方法mem_write被調用;最後從設備中讀出數據以驗證讀出的數據是否與寫入的數據相同,設備方法mem_read被調用。)
14、卸載內核模塊
#rmmod memdev
#lsmod
(說明:內核模塊被卸載時,模塊卸載函數memdev_exit被調用,一般來說模塊卸載函數主要完成於模塊加載函數相反的工作。本實驗模塊卸載函數完成設備註銷,釋放內存,釋放設備號等操作。)
15、實驗總結:
本實驗使用內存模擬字符設備,實驗其讀寫,定位操作。實驗本身並不難,重點是要我們掌握字符設備驅動設計規範,掌握設備方法(read,write,release.llseek)調用實機,實現方式。