日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

【操作系统实验】设备驱动(Linux环境下)

發布時間:2023/12/10 linux 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【操作系统实验】设备驱动(Linux环境下) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

【實驗目的】
實驗目的:熟悉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 //輸出:linux-headers-3.13.0-32,linux-headers-3.13.0-32-generic apt-cache search linux-source //查看一下可一下載的源碼包 sudo apt-get install linux-source-3.13.0 //下載完成后,在/usr/sr下會有壓縮包,然后解壓 make oldconfig //開始配置內核 選擇最快的原版的配置方式,menuconfig , xconfig也行 make bzImage //執行結束后,可以看到在當前目錄下生成了一個新的文件: vmlinux 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://vault.centos.org/7.7.1908/updates/Source/SPackages /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);/*把數據復制到應用程序空間從內核空間demoBuffer中拷貝數據到用戶空間buf中*/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, };/******************************************************* MODULEROUTINE *******************************************************/ static void __exit simple_cleanup_module(void) {unregister_chrdev(devMajor,"mydm1");printk(" : simple_cleanup_module!\n");//return 0; }static int __init simple_init_module(void) {int ret;//根據設備號與設備名注冊字符設備,此處設備名不一定要與模塊名相同,/*register_chrdev函數用于在內核空間,把驅動和/dev下設備文件鏈接在一起*/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 /*全局內存最大4KB*/ #define MEM_CLEAR 0x1 /*清零全局內存*/ #define GLOBALMEM_MAJOR 354static int globalmem_major = GLOBALMEM_MAJOR;/*預設的globalmem的主設備號*//*globalmem的設備結構體:包含了對應于globalmem字符設備的cdev 和 使用內存mem[GLOBALMEM_SIZE]*/ struct globalmem_dev {struct cdev 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; }/*設備控制函數:ioctl()函數接受的MEM_CLEAR命令,這個命令將全局內存的有效數據長度清零,對于設備不支持的命令,ioctl()函數應該返回-EINVAL*/ static long globalmem_ioctl( /*struct inode *inodep,*/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; }/*讀函數:讀寫函數主要是讓設備結構體的mem[]數組與用戶空間交互數據,并隨著訪問字節數變更返回用戶的文件讀寫偏移位置*/ 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; }/*seek文件定位函數:seek()函數對文件定位的起始地址可以是文件開頭(SEEK_SET,0)、當前位置(SEEK_CUR,1)、文件尾(SEEK_END,2)*/ 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, // .ioctl = globalmem_ioctl,.unlocked_ioctl = globalmem_ioctl,.open = globalmem_open,.release = globalmem_release, };/*初始化并注冊cdev*/ 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); //注銷cdevkfree(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)實驗過程截圖
主要代碼如下;

/*globalfifo設備結構體*/struct globalfifo_dev {struct cdev cdev; //cdev結構體unsigned int current_len; //fifo有效數據長度unsigned char mem[GLOBALFIFO_SIZE];struct semaphore sem; //并發控制用的信號量wait_queue_head_t r_wait; //阻塞讀用的等待隊列 內核雙向循環鏈表 都可以為頭wait_queue_head_t w_wait; //阻塞寫用的等待隊列頭 }; /*globalfifo讀函數*/ 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); /*加入讀等待隊列頭 到內核*//*等待FIFO非空*/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獲取信號量以及釋放信號量。 /*ioctl 設備控制函數*/ static long globalfifo_ioctl(/*struct inode *inodep,*/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);/*fifo非空*/if(dev->current_len != 0){mask |= POLLIN | POLLRDNORM; /*標示數據可以獲得*/}/*fifo 非滿*/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环境下)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。