【實驗目的】
實驗目的:熟悉Linux下驅動程序設計
?????編譯內核
實驗要求:在Linux系統下,編譯內核,并在該內核下完成實驗;
?????自主設計驅動程序,完成驅動程序的安裝
【實驗內容】
1.編譯內核,構造內核源碼樹
2.ubantu14.04 32位下編寫hello world程序以及加載驅動
3.ubantu14.04第二個memory驅動程序
4.ubantu14.04第三個使用文件私有數據的globalmem的設備驅動
5.Linux設備驅動中的阻塞與非阻塞I/O
【實驗環境】(含主要設計設備、器材、軟件等)
Pc電腦一臺
【實驗步驟、過程】(含原理圖、流程圖、關鍵代碼,或實驗過程中的記錄、數據等)
1.編譯內核,構造內核源碼樹(itc-centos)
(1)編譯指令
①指導書上的指令
uname
-r
ls
/usr
/src
apt
-cache search linux
-source
sudo apt
-get install linux
-source
-3.13.0
make oldconfig
make bzImage
make modules和make modules_install
②自己查閱的資料
??以一個不是root用戶的戶口,創建一個以~/rpmbuild為基礎的目錄樹:
[user@host
] $ mkdir
-p
~/rpmbuild
/{BUILD
,BUILDROOT
,RPMS
,SOURCES
,SPECS
,SPRMS
}
[user@host
] $ echo ‘
%_topdir
% (echo $HOME
) / rpmbuild’
> ~/.rpmmacros
??以一個不是root用戶的普通戶口,執行以下指令來安裝源代碼組件:
[user@host
] $ rpm
-i http
:
/kernel
-3.10.0-1062.9.1.e17.src
.rpm
2>&1 | grep
-v exist
解壓及預備源代碼文件:
[user@host
] $ cd
~/rpmbuild
/SPECS
[user@host SPECS
] $ rpmbuild
-bp
--target
= $
(uname
-m
) kernel
.spec
??其中,$(uname -m)這條命令,可以將目標結構設置為你現有的內核的結構,一般來說這是可行的,因為多數人需要以i686或x86_64為目標。
(2)編譯過程截圖
????????????圖1 編譯內核過程
(3)編譯過程注意事項
??如果這一步中沒有建立內核源碼樹,按下面步驟進行,雖然能夠生成hello.ko,但執行sudo insmod hello.ko后,執行lsmod會沒反應,導致系統報告問題,會導致下次開機或重啟時有問題,若啟動不了,可以進入recovery模式,執行fsck,開機時做嵌入式linux開發一般在PC機上編譯好了,下到板子上去運行,板子上的linux內核和PC機上的linux版本很多時候都是不一樣的,比如pc機上的是linux2.6,板子上的是linux3.1,這個時候就要下linux3.1的內核,用它編譯的驅動模塊在板子上才能加載上,不然會出錯。
??在執行最后一條指令make modules和make modules_install時,執行結束之后,會在/lib/modules下生成新的目錄/lib/modules/3.13.0-32-generic/,但若由于主機本身內核版本就為3.13.0-32-generic,所以/lib/modules/3.13.0-32-generic/本身就存在,此時這兩條指令make modules和make modules_install就不需要執行了。
2.ubantu14.04 32位下編寫hello world程序以及加載驅動
(1)實驗步驟
①編寫hello. c程序并寫Makefile文件;
②之后通過make指令,生成hello.ko等其他文件;
③執行sudo insmod hello.ko,在lsmod即可驗證模塊是否已裝載,最后rmmod移出模塊;
④通過cat /var/log/syslog | grep world來觀察其輸出。
(2)實驗過程截圖
??????????圖2 hello.c代碼
??????????圖3 對于Makefile內容
??????????圖4 執行make指令
??????????圖5 執行lsmod指令
??????????圖6 運行結果
(3)實驗過程注意事項
$
(MAKE
) -C $
(KERNELDIR
) M
=$
(PWD
) modules
??這句是Makefile的規則:這里的$(MAKE)就相當于make,-C 選項的作用是指將當前工作目錄轉移到你所指定的位置。“M=”選項的作用是,當用戶需要以某個內核為基礎編譯一個外部模塊的話,需要在make modules 命令中加入“M=dir”,程序會自動到你所指定的dir目錄中查找模塊源碼,將其編譯,生成ko文件。
??一定要實現查看電腦Linux的內核版本,機房不同電腦的內核版本可能不一樣,要事先查好之后并且在Makefile文件中版本號要改。
??用printk,內核會根據日志級別,可能把消息打印到當前控制臺上,這個控制臺通常是一個字符模式的終端、一個串口打印機或是一個并口打印機。這些消息正常輸出的前提是──日志輸出級別小于console_loglevel(在內核中數字越小優先級越高)。
3.ubantu14.04第二個memory驅動程序
(1)實驗步驟
①編寫c程序并寫Makefile文件;
②根據設備號,從內核空間將數據取出,寫入相應的設備空間內;
③通過程序創建主從設備,將數據寫入;
④再通過模塊功能讀出,觀察其輸出。
(2)實驗過程截圖
①到包含Makefile和mydm1.c的目錄下執行make,生成mydm1.ko;
②執行sudo insmod mydm1.ko;
③驗證:lsmod | grep mydm1
④需要創建一個文件(該設備文件用于和設備驅動操作)
mknod /dev/fgj c 224 0 c代表字符設備 224為主設備號,0為從設備號
⑤ gcc test.c
主要代碼如下:
1.mydm1.c:
int devMajor
= 224; static unsigned char simple_inc
= 0;
static unsigned char demoBuffer
[256]; int simple_open( struct inode *inode
, struct file *filp
)
{printk(" : open Success!\n");if(simple_inc
>0){return -1;}simple_inc
= simple_inc
+ 1;return 0;
}int simple_release(struct inode *inode
, struct file *filp
)
{printk(": release Success%d!\n",devMajor
);simple_inc
= simple_inc
- 1;return 0;
}ssize_t
simple_read(struct file *filp
,char __user
*buf
,size_t count
,loff_t
*f_pos
)
{printk(": read Success%d!\n", devMajor
);if( copy_to_user(buf
,demoBuffer
,count
) ) {count
=-EFAULT
;}return count
;
}ssize_t
simple_write(struct file *filp
, const char __user
*buf
,size_t count
,loff_t
*f_pos
)
{printk(": write Success%d!\n",devMajor
);if(copy_from_user(demoBuffer
+ *f_pos
,buf
,count
)){count
=-EFAULT
;}return count
;
}struct file_operations simple_fops
={
.owner
=THIS_MODULE
,
.read
=simple_read
,
.write
=simple_write
,
.open
=simple_open
,
.release
=simple_release
,
};
static void __exit
simple_cleanup_module(void)
{unregister_chrdev(devMajor
,"mydm1");printk(" : simple_cleanup_module!\n");
}static int __init
simple_init_module(void)
{int ret
;ret
=register_chrdev( devMajor
,"mydm1",&simple_fops
);if(ret
<0){printk(" : Unabletoregistercharacterdevice%d!\n",devMajor
);return ret
;}else{printk(" : Success%d!\n",devMajor
);}return 0;
}module_init(simple_init_module
);
module_exit(simple_cleanup_module
);
2.Makefile:
obj
-m
:= mydm1
.o
#KERNELDIR := /lib/modules/3.16.61-32-generic/build
KERNELDIR
:= /lib
/modules
/$
(shell uname
-r
)/build
PWD
:= $
(shell pwd
)
modules
:$
(MAKE
) -C $
(KERNELDIR
) M
=$
(PWD
) modules
modules_install
:$
(MAKE
) -C $
(KERNELDIR
) M
=$
(PWD
) modules_install
clean
:rm
*.o
*.ko
*.mod
.c
*.order
*.symvers
3.test.c:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>void main(void)
{int fd
;int i
;char data
[256];int retval
;fd
= open("/dev/fgj",O_RDWR
);if(fd
==-1){perror("erroropen\n");exit(-1);}printf("open/dev/fgjsuccessfully\n");retval
= write(fd
,"fgj",3);if(retval
==-1){perror("writeerror\n");exit(-1);}printf("write successfully\n");retval
=read(fd
,data
,3);if(retval
==-1){perror("readerror\n");exit(-1);}data
[retval
]=0;printf("readsuccessfully:%s\n",data
);close(fd
);
}
運行結果如下:
?? ?? ?? ?? ??圖7 執行make命令
?? ?? ?? ?? ??圖8 執行結果
(3)實驗過程注意事項
①保證/dev/fgj有讀寫權限 chmod 666 /dev/fgj;
②printk打印的消息若滅有在控制臺上顯示,可以同構dmesg命令或查看系統的日志文件cat /var/log/syslog命令看到;
③驅動編譯:gcc必須用系統自帶的版本,gcc -v查看當前版本, 否則insmod后,lsmod會沒反應,系統直接卡死,注銷也不行,只能重啟;
4.ubantu14.04第三個使用文件私有數據的globalmem的設備驅動
(1)實驗步驟
①編寫c程序并寫Makefile文件;
②執行:make ,然后sudo insmod globalmem.ko;
③驗證:lsmod 可以看到該模塊,mknod /dev/globalmem c 354 0, echo ‘good nihao’ > /dev/globalmem, cat /dev/globalmem可以看到輸出good nihao;
(2)實驗過程截圖
代碼如下:
#define GLOBALMEM_SIZE 0X1000
#define MEM_CLEAR 0x1
#define GLOBALMEM_MAJOR 354static int globalmem_major
= GLOBALMEM_MAJOR
;
struct globalmem_dev
{struct cdev cdev
; unsigned char mem
[GLOBALMEM_SIZE
];
};struct globalmem_dev *globalmem_devp
;
int globalmem_open(struct inode *inode
,struct file *filp
)
{filp
->private_data
= globalmem_devp
; return 0;
}
int globalmem_release(struct inode *inode
,struct file *filp
)
{return 0;
}
static long globalmem_ioctl( struct file *filp
,unsigned int cmd
,unsigned long arg
)
{struct globalmem_dev *dev
= filp
->private_data
; switch(cmd
){case MEM_CLEAR
:memset(dev
->mem
,0,GLOBALMEM_SIZE
);printk(KERN_INFO
"globalmem is set to zero\n");break;default:return -EINVAL
;}return 0;
}
static ssize_t
globalmem_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 globalmem_dev *dev
= filp
->private_data
; if(p
>= GLOBALMEM_SIZE
) {return count
? -ENXIO
:0;}if(count
> GLOBALMEM_SIZE
- p
){count
= GLOBALMEM_SIZE
- p
;}if(copy_to_user(buf
,(void *)(dev
->mem
+p
),count
)) {ret
= -EFAULT
;}else{*ppos
+= count
;ret
= count
;printk(KERN_INFO
"read %d bytes(s) from %ld\n",count
,p
);}return ret
;
}
static ssize_t
globalmem_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 globalmem_dev *dev
= filp
->private_data
;if(p
>= GLOBALMEM_SIZE
) {return count
? -ENXIO
:0;}if(count
> GLOBALMEM_SIZE
- p
){count
= GLOBALMEM_SIZE
- p
;}if(copy_from_user(dev
->mem
+ p
,buf
,count
)) {ret
= -EFAULT
;}else{*ppos
=+ count
;ret
= count
;printk(KERN_INFO
"written %d bytes(s) from %ld\n",count
,p
);}return ret
;
}
static loff_t
globalmem_llseek(struct file *filp
,loff_t offset
,int orig
)
{loff_t ret
= 0;switch(orig
){case 0: if(offset
<0 ){ret
= -EINVAL
;break;}if((unsigned int )offset
> GLOBALMEM_SIZE
){ret
= - EINVAL
;break;}filp
->f_pos
= (unsigned int)offset
;ret
= filp
->f_pos
;break;case 1: if((filp
->f_pos
+ offset
) > GLOBALMEM_SIZE
){ret
= -EINVAL
;break;}if((filp
->f_pos
+ offset
)<0){ret
= -EINVAL
;break;}filp
->f_pos
+=offset
;ret
= filp
->f_pos
;break;default:ret
= -EINVAL
;break; }return ret
;
}
static const struct file_operations globalmem_fops
=
{.owner
= THIS_MODULE
,.llseek
= globalmem_llseek
,.read
= globalmem_read
,.write
= globalmem_write
,
.unlocked_ioctl
= globalmem_ioctl
,.open
= globalmem_open
,.release
= globalmem_release
,
};
static void globalmem_setup_cdev(struct globalmem_dev *dev
,int index
)
{int err
,devno
= MKDEV(globalmem_major
,index
);cdev_init(&dev
->cdev
,&globalmem_fops
);dev
->cdev
.owner
= THIS_MODULE
;dev
->cdev
.ops
= &globalmem_fops
;err
= cdev_add(&dev
->cdev
,devno
,1);if(err
){printk(KERN_NOTICE
"Error %d adding LED%d",err
,index
);}
}
static int __init
globalmem_init(void)
{int result
;dev_t devno
= MKDEV(globalmem_major
,0);if(globalmem_major
) {result
= register_chrdev_region(devno
,1,"globalmem");}else {result
= alloc_chrdev_region(&devno
,0,1,"globalmem");globalmem_major
= MAJOR(devno
);}if(result
< 0){return result
;}globalmem_devp
= kmalloc(sizeof(struct globalmem_dev),GFP_KERNEL
); if(!globalmem_devp
){result
= -ENOMEM
;goto fail_malloc
;}memset(globalmem_devp
,0,sizeof(struct globalmem_dev));globalmem_setup_cdev(globalmem_devp
,0);return 0;fail_malloc
:unregister_chrdev_region(devno
,1);return result
;
}
static void __exit
globalmem_exit(void)
{cdev_del(&globalmem_devp
->cdev
); kfree(globalmem_devp
); unregister_chrdev_region(MKDEV(globalmem_major
,0),1);
}MODULE_AUTHOR("Song Baohua");
MODULE_LICENSE("Dual BSD/GPL");module_param(globalmem_major
,int,S_IRUGO
);module_init(globalmem_init
);
module_exit(globalmem_exit
);
2.Makefile:
KERNELDIR
:= /lib
/modules
/$
(shell uname
-r
)/build
obj
-m
:= globalmem
.o
modules
:$
(MAKE
) -C $
(KERNELDIR
) M
=$
(PWD
) modules
modules_install
:$
(MAKE
) -C $
(KERNELDIR
) M
=$
(PWD
) modules_installclean
:make
-C $
(KERNELDIR
) M
=`pwd` modules clean rm
-rf modules
.order
??????????圖9 執行make指令
??????????圖10 執行lsmod指令
??????????圖11 驗證結果
5.Linux設備驅動中的阻塞與非阻塞I/O
(1)實驗內容
??阻塞和非阻塞I/O是設備訪問的兩種不同模式,驅動程序可以靈活的支持用戶空間對設備的這兩種訪問方式。本例子講述了這兩者的區別并實現I/O的等待隊列機制,并進行了用戶空間的驗證。
基本概念:
1> 阻塞操作是指在執行設備操作時,若不能獲得資源,則掛起進程直到滿足操作條件后再進行操作。被掛起的進 程進入休眠,被從調度器移走,直到條件滿足。
2> 非阻塞操作在不能進行設備操作時,并不掛起,它或者放棄,或者不停地查詢,直到可以進行操作。非阻塞應用程序通常使用select系統調用查詢是否可以對設備進行無阻塞的訪問最終會引發設備驅動中poll函數執行。
(2)實驗過程截圖
主要代碼如下;
struct globalfifo_dev
{struct cdev cdev
; unsigned int current_len
; unsigned char mem
[GLOBALFIFO_SIZE
];struct semaphore sem
; wait_queue_head_t r_wait
; wait_queue_head_t w_wait
;
};
static ssize_t
globalfifo_read(struct file *filp
, char __user
*buf
, size_t count
, loff_t
*ppos
)
{int ret
;struct globalfifo_dev *dev
= filp
->private_data
;DECLARE_WAITQUEUE(wait
, current
);down(&dev
->sem
); add_wait_queue(&dev
->r_wait
, &wait
); if(dev
->current_len
== 0){if(filp
->f_flags
& O_NONBLOCK
){ ret
= -EAGAIN
;goto out
;}__set_current_state(TASK_INTERRUPTIBLE
); up(&dev
->sem
); schedule(); if(signal_pending(current
)){ret
= -ERESTARTSYS
;goto out2
;}down(&dev
->sem
);}
通過Up和down獲取信號量以及釋放信號量。
static long globalfifo_ioctl(struct file *filp
, unsigned int cmd
, unsigned long arg
)
{struct globalfifo_dev *dev
= filp
->private_data
;switch(cmd
){case FIFO_CLEAR
:down(&dev
->sem
); dev
->current_len
= 0;memset(dev
->mem
,0,GLOBALFIFO_SIZE
);up(&dev
->sem
); printk(KERN_INFO
"globalfifo is set to zero");break;default:return -EINVAL
;}return 0;
}
加入輪詢方式
,判斷FIFO空和滿。
static unsigned int globalfifo_poll(struct file *filp
, poll_table
*wait
)
{unsigned int mask
= 0;struct globalfifo_dev *dev
= filp
->private_data
;down(&dev
->sem
);poll_wait(filp
, &dev
->r_wait
, wait
);poll_wait(filp
, &dev
->w_wait
, wait
);if(dev
->current_len
!= 0){mask
|= POLLIN
| POLLRDNORM
; }if(dev
->current_len
!= GLOBALFIFO_SIZE
){mask
|= POLLOUT
| POLLWRNORM
; }up(&dev
->sem
);return mask
;
}
????????????圖12 運行結果1
????????????圖13 運行結果2
【實驗結果或總結】(對實驗結果進行相應分析,或總結實驗的心得體會,并提出實驗的改進意見)
1.在編寫 hello world 程序以及加載驅動的時候,module_init(hello_init); module_exit(hello_exit); 這兩個是函數的入口地址和出口地址。同時Makefile文件記得更改KERNELDIR := /lib/modules/3.16.61-32-generic/build的版本號,包括32也需要改成自己電腦相匹配的。
2.$(MAKE) -C (KERNELDIR)M=(KERNELDIR) M=(KERNELDIR)M=(PWD) modules 這句是 Makefile 的規則:這里的$(MAKE)就相當于 make,-C 選項的作用是指將當前,工作目錄轉移到你所指定的位置。“M=”選項的作用是,當用戶需要以某個內核為基礎。
3.通過查閱相關資料,我明白了Makefile文件的作用,它相當于是覆寫了make指令,讓make指令的指向目標變為當前目錄下的文件了。而sudo的作用在于,普通用戶在安裝時,是沒有權限的,因此要通過sudo來獲取超級用戶權限。
4.在第二個memory驅動程序中 ret=register_chrdev( devMajor,“mydm1”,&simple_fops); register_chrdev 函數用于在內核空間,把驅動 和/dev 下設備文件鏈接在一起,mydm1.c :read()函數,將內核空間中的數據復制到用戶空間中,從而讀取出數據。在驗證過程中,首先是在內核的設備區創建了對應程序的設備區,再通過測試程序往里面寫入數據,最后再在用戶空間中讀出來。Write()函數則相反。
5.Makefile 里的KERNELDIR := /lib/modules/$(shell uname -r)/build 則是用腳本的方法獲取 uname -r 即可不用對代碼進行修改,動態獲取內核的版本信息。
6.使用文件私有數據的globalmem的設備驅動 :用到了內核區的字符設備,將輸入的字符存儲到全局內存區域,再從用戶空間中訪問這個區域,獲取到其中的數據。struct globalmem_dev { struct cdev cdev; //cdev 結構體 unsigned char mem[GLOBALMEM_SIZE]; //全局內存 }; globalmem 的設備結構體:包含了對應于 globalmem 字符設備的 cdev 和 使用內存 mem[GLOBALMEM_SIZE],其他與上一個實驗類似。增加了一個ioctl函數:ioctl()函數接受的 MEM_CLEAR 命令,這個命令將全局內存的有效數據長度 清零,對于設備不支持的命令,ioctl()函數應該返回-EINVAL。,mknod /dev/globalmem c 354 0, echo ‘good nihao’ > /dev/globalmem, cat /dev/globalmem 即可驗證輸出 ,cat是讀出,echo是寫入。
??最后感謝倪福川老師以及各位同學給我的幫助,通過一學期對于操作系統理論和實驗的學習,讓我對操作系統加深了理解,在自己原有知識的基礎上得到了更深層次的感悟,我也會把在操作系統課程之中學到的知識應用到今后的學習生活中去,思考問題也要更細致,要學會從底層去思考問題的起源,并學會從底層去解決問題。最后再一次感謝倪福川老師這一學期以來的諄諄教誨,我定當銘記于心,認真完成后續課程的學習。
總結
以上是生活随笔為你收集整理的【操作系统实验】设备驱动(Linux环境下)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。