linux添加模块设备,linux采用模块方法,添加一个新的设备
繼續上次的操作系統實驗。這次需要添加一個新的設備,我選擇添加一個比較好實現的字符設備。先說點基礎知識吧:
系統調用是操作系統內核和應用程序之間的接口,而設備驅動程序是操作系統內核和機器硬件之間的接口。
設備驅動程序為應用程序屏蔽了硬件的細節,這樣在應用程序看來,硬件設備只是一個設備文件, 應用程序可以像操作普通文件一樣對硬件設備進行操作。
設備驅動程序是內核的一部分,它完成以下的功能:
(1) 對設備初始化和釋放.
(2) 把數據從內核傳送到硬件和從硬件讀取數據.
(3) 讀取應用程序傳送給設備文件的數據和回送應用程序請求的數據.
(4) 檢測和處理設備出現的錯誤.
Linux支持三中不同類型的設備:字符設備(character devices)、塊設備(block devices)和網絡設備(network interfaces)。
字符設備和塊設備的主要區別是:在對字符設備發出讀/寫請求時,實際的硬件I/O一般就緊接著發生了。
塊設備則不然,它利用一塊系統內存作緩沖區,當用戶進程對設備請求能滿足用戶的要求,就返回請求的數據,如果不能,就調用請求函數來進行實際的I/O操作。塊設備是主要針對磁盤等慢速設備設計的,以免耗費過多的CPU時間來等待.
用戶進程是通過設備文件來與實際的硬件打交道,每個設備文件都都有其文件屬性(c/b),表示是字符設備還是塊設備。
另外每個文件都有兩個設備號,第一個是主設備號,標識驅動程序,第二個是從設備號,標識使用同一個設備驅動程序的不同的硬件設備,
比如有兩個軟盤,就可以用從設備號來區分他們.設備文件的的主設備號必須與設備驅動程序在登記時申請的主設備號一致,否則用戶進程將無法訪問到驅動程序.。
設備驅動程序工作的基本原理:
用戶進程利用系統調用對設備進行諸如read/write操作,系統調用通過設備文件的主設備號找到相應的設備驅動程序,然后讀取這個數據結構相應的函數指針,接著把控制權交給該函數。
最后,在用戶進程調用驅動程序時,系統進入核心態,這時不再是搶先式調度。也就是說,系統必須在你的驅動程序的子函數返回后才能進行其他的工作。
如果你的驅動程序陷入死循環,你只有重新啟動機器了。
下面我們就來添加一個字符設備:
編寫設備驅動源代碼
在設備驅動程序中有一個非常重要的結構file_operations,該結構的每個域都對應著一個系統調用。
用戶進程利用系統調用對設備文件進行諸如read/write操作時,系統調用通過設備文件的主設備號找到相應的設備驅動程序,然后讀取這個數據結構相應的函數指針,接著把控制權交給該函數。
structfile_operations?{
int(*seek)?(structinode?*?,structfile?*,?off_t?,int);
int(*read)?(structinode?*?,structfile?*,char,int);
int(*write)?(structinode?*?,structfile?*,?off_t?,int);
int(*readdir)?(structinode?*?,structfile?*,structdirent?*?,int);
int(*select)?(structinode?*?,structfile?*,int,select_table?*);
int(*ioctl)?(structinode?*?,structfile?*,?unsinedint,unsignedlong);
int(*mmap)?(structinode?*?,structfile?*,structvm_area_struct?*);
int(*open)?(structinode?*?,structfile?*);
int(*release)?(structinode?*?,structfile?*);
int(*fsync)?(structinode?*?,structfile?*);
int(*fasync)?(structinode?*?,structfile?*,int);
int(*check_media_change)?(structinode?*?,structfile?*);
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相應的函數體。
例如:
staticssize_t?my_open(structinode?*inode,structfile?*file){
staticintcounter=0;
if(Device_Open)
return-EBUSY;
Device_Open++;
/*寫入設備的信息*/
sprintf(msg,"the?device?has?been?called?%d?times\n",counter++);
msg_ptr=msg;
return0;
}
同時對于可卸載的內核模塊(LKM),至少還有兩個基本的模塊:
例如本例中的:
staticint__init?my_init(void){
intresult;
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");
}
return0;
}
staticvoid__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?
#include?
#include?
#include?
#include?
#if?CONFIG_MODVERSIONS?==?1
#define?MODVERSIONS
#include?
#endif
#define?DEVICE_NUM?0?//隨機產生一個設備號
staticintdevice_num?=?0;//用來保存創建成功后的設備號
staticcharbuffer[1024]?="mydriver";//數據緩沖區
staticintopen_nr?=?0;//打開設備的進程數,用于內核的互斥
//函數聲明
staticintmydriver_open(structinode?*inode,structfile?*filp);
staticintmydriver_release(structinode?*inode,structfile*?filp);
staticssize_t?mydriver_read(structfile?*file,char__user?*buf,size_tcount,?loff_t?*f_pos);
staticssize_t?mydriver_write(structfile?*file,constchar__user?*buf,size_tcount,?loff_t?*f_pos);
//填充file_operations結構相關入口
staticstructfile_operations?mydriver_fops?=?{
.read????=?mydriver_read,
.write???=?mydriver_write,
.open????=?mydriver_open,
.release?=?mydriver_release,
};
//打開函數
staticintmydriver_open(structinode?*inode,structfile?*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);
return0;
}
else{
printk(KERN_ALERT?"Another?process?open?the?char?device.\n");//進程掛起
return-1;
}
}
//讀函數
staticssize_t?mydriver_read(structfile?*file,char__user?*buf,size_tcount,?loff_t?*f_pos)
{
//if?(buf?==?NULL)?return?0;
if(copy_to_user(buf,?buffer,sizeof(buffer)))//讀緩沖
{
return-1;
}
returnsizeof(buffer);
}
//寫函數,將用戶的輸入字符串寫入
staticssize_t?mydriver_write(structfile?*file,constchar__user?*buf,size_tcount,?loff_t?*f_pos)
{
//if?(buf?==?NULL)?return?0;
if(copy_from_user(buffer,?buf,sizeof(buffer)))//寫緩沖
{
return-1;
}
returnsizeof(buffer);
}
//釋放設備函數
staticintmydriver_release(structinode?*inode,structfile*?filp)
{
open_nr--;?//進程數減1
printk("The?device?is?released!\n");
module_put(THIS_MODULE);
return0;
}
//注冊設備函數
staticint__init?mydriver_init(void)
{
intresult;
printk(KERN_ALERT?"Begin?to?init?Char?Device!");//注冊設備
//向系統的字符登記表登記一個字符設備
result?=?register_chrdev(DEVICE_NUM,?"mydriver",?&mydriver_fops);
if(result?
printk(KERN_WARNING?"mydriver:?register?failure\n");
return-1;
}
else{
printk("mydriver:?register?success!\n");
device_num?=?result;
return0;
}
}
//注銷設備函數
staticvoid__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?
#include?
#include?
#include?
#include?
intmain(void)
{
intfd;
charbuf[1024];
charget[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);//釋放設備
return0;
}
gcc -o mydriver_test mydriver_test.c
./mydriver_test
輸入任意字符串,驅動程序將字符串拷貝進新加入的設備,然后再讀取出來,設備中保留字符串信息,再次輸入將覆蓋原來的信息。
ALL ended~~I will be back~~
總結
以上是生活随笔為你收集整理的linux添加模块设备,linux采用模块方法,添加一个新的设备的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 单片机原理及应用 张鑫_单片机原理及应用
- 下一篇: linux 其他常用命令