收藏 分销(赏)

Linux内核模块课程设计.doc

上传人:xrp****65 文档编号:8974085 上传时间:2025-03-09 格式:DOC 页数:15 大小:876KB
下载 相关 举报
Linux内核模块课程设计.doc_第1页
第1页 / 共15页
Linux内核模块课程设计.doc_第2页
第2页 / 共15页
点击查看更多>>
资源描述
Linux课程设计 项目名称: 内核模块编程 姓 名: ***** 班 级: 计算机*班 学 号: 2008**** 指导教师: ** 日 期: 2011.6.19—2011.6.24 目录 一.课程设计目的 2 二.任务描述 2 三.选题原因 2 四、准备阶段 3 五.整体思路 8 六.程序代码 8 七.遇到的问题 12 八.测试结果 13 九.总结 15 一.课程设计目的 通过课程设计对操作系统基本原理进行更深入的认识,以Linux为具体研究对象,分析理解操作系统底层实现,综合利用已学知识与技术,就Linux操作系统各功能方面进行模拟或实现 二.任务描述 编写proc文件系统相关的内核模块: 设计一个模块,该模块功能是列出系统中所有内核线程的程序名、PID号和进程状态。再设计一个带参数的模块,参数为进程的PID号,功能是列出进程的家族信息,包括父进程、兄弟进程和子进程的程序名、PID号。 三.选题原因 学习操作系统时,没有把内核这一块学好,所以想借此机会加强对操作系统底层的理解。 四、准备阶段 看到此题时不知道proc文件的作用,对内核的概念也很模糊,并且错误的以为内核利用一些命令来调用进程呢。很多东西都不理解,所以首先是看书上网逐一进行学习。下面是查找的一些资料。 1.关于linux内核编程书本上有详细介绍并且也比较好理解。首先把书上例题演示了一遍,对模块编程就掌握的差不多了。 模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行,这与运行在用户空间的进程是不同的。模块通常由一组函数和数据结构组成,用来实现一种文件系统、一个驱动程序或其他内核上层的功能。 应用程序与内核模块程序的比较 $sN[ _H;`1o7f0 C语言应用程序LUPA开源社区#t)VsE+s7T    内核模块程序LUPA开源社区1~|-CX*C*U%s"q4L1p 使用函数LUPA开源社区g(`D c'hD   Libc库LUPA开源社区)l-KY5V:e4[5b    内核函数7si zK)O*n0 运行空间aWx7`h0   用户空间LUPA开源社区#I wYfTV+H    内核空间$~?k(e u7HQY4I0 运行权限!e{ O;ba`8X#H0   普通用户LUPA开源社区:X} AJ%A4u    超级用户LUPA开源社区W7P!MUbMLP 入口函数&xBJ(H:B[ H0    main()LUPA开源社区;fC.U o6F%e r H@8H   module_init()4f RQV j KZ~M6F0 出口函数LUPA开源社区2r)}v0Q%wWI    exit()LUPA开源社区cSr,o/Vzi   module_exit()LUPA开源社区v%K$IgD$j5[?(a 编译LUPA开源社区h%Ny mUZ2qt!A   Gcc –cLUPA开源社区@3_;M[[+@   Makefile 连接+H(iB f.w$v7J0   GccLUPA开源社区&wZKg!o&L   insmodLUPA开源社区Egc0E y C6O W 运行LUPA开源社区*o_$\&`G   直接运行LUPA开源社区'o!LK-Qo#dm D     insmodsS#HOn` v0 调试LUPA开源社区uT-Q;kd7x   Gdb~X.hw~ dP ]0 kdbug, kdb,kgdb等LUPA开源社区T ?o#F.H;m(Bg 2. 编译工具make :Make工具通过一个称为 Makefile 的文件来完成并自动维护编译工作。 课本上有提供一个超级通用的Makefile,此文件要与模块在同一个文件夹下。 ifneq ($(KERNELRELEASE),) obj-m += nullparam.o else PWD := $(shell pwd) KVER := $(shell uname -r) KDIR := /lib/modules/$(KVER)/build all: $(MAKE) -C $(KDIR) M=$(PWD) clean: rm -rf *.o *.mod.c *.ko *.symvers *.order *.markers endif KERNELRELEASE是在内核源码的顶层Makefile中定义的一个变量,在第一次读取执行此Makefile时,KERNELRELEASE没有被定义, 所以make将读取执行else之后的内容。如果make的目标是clean,直接执行clean操作,然后结束。当make的目标为all时,-C $(KDIR) 指明跳转到内核源码目录下读取那里的Makefile;M=$(PWD) 表明然后返回到当前目录继续读入、执行当前的Makefile。当从内核源码目录返回时,KERNELRELEASE已被被定义,kbuild也被启动去解析kbuild语法的语句,make将继续读取else之前的内容。else之前的内容为kbuild语法的语句, 指明模块源码中各文件的依赖关系,以及要生成的目标模块名。obj-m += nullparam.o表示编译连接后将生成nullparam.o模块 2.用户级线程:指不需要内核支持而在用户程序中实现的线程,其不依赖于操作系统核心,应用进程利用线程库提供创建、同步、调度和管理线程的函数来控制用户线程。 内核线程:指需要内核的参与,由内核完成线程的调度。其依赖于操作系统核心,由内核的内部需求进行创建和撤销。 3.proc文件系统:proc即process的缩写,最初的proc文件系统只是存放进程的相关信息。但现在的/proc文件系统除此之外还包含系统的状态信息和配置信息。 /proc文件系统相当于内核的一个快照,该目录下的所有信息都是动态的从正在运行的内核中读取。 基于这种原因,/proc文件系统就成为了用户和内核之间交互的接口。一方面,用户可以从/proc文件系统中读取很多内核释放出来的信息;另一方面,内核也可以在恰当的时候从用户那里得到输入信息,从而改变内核的相关状态和配置。 相比传统的文件系统,/proc是一种特殊的文件系统,即虚拟文件系统。这里的虚拟是强调/proc文件系统下的所有文件都存在于内存中而不是磁盘上。也就是说/proc文件系统只占用内存空间,而不占用系统的外存空间。 /proc 虚拟文件系统可以广泛地用来报告内核的信息,也可以用来进行动态配置。它对于驱动程序和模块编程来说都是非常完整的。 4. proc_dir_entry结构: proc文件系统的组织结构是proc_dir_entry结构,所有属于proc文件系统的文件,都对应一个proc_dir_entry结构,这个结构在proc_fs.h中定义。 struct proc_dir_entry { //用来唯一标志proc_dir_entry结构的节点号 unsigned short low_ino; unsigned short namelen; //这个proc文件的名字。 const char *name; //该proc文件的模式, 第一部分是文件的类型,第二部分是该文件的权限。 mode_t mode; nlink_t nlink; uid_t uid; gid_t gid; //即我们使用“ls”命令时,所显示出的文件大小。 unsigned long size; struct inode_operations * proc_iops; /* 是一个inode_operations结构,其中设置了针对这个proc索引节点的操作函数,这样,我们就可以针对不同类型的proc文件,提供不同的方法,以完成不同的工作。*/ struct file_operations * proc_fops; /*是一个file_operations结构,其中放置了针对这个proc文件的操作函数,我们可以把对proc文件的读写操作,放在这个结构中,用以实现对/proc目录中的文件的读,写功能。*/ get_info_t *get_info; /*当用户向proc文件读取的数据小于一个页面大小时,可以使用这个函数向用户返回数据。*/ struct module *owner; struct proc_dir_entry *next, *parent, *subdir; /*使用这些链表,在内存中,proc_dir_entry结构就以树的形式链接在一起。 */ void *data; read_proc_t *read_proc; write_proc_t *write_proc; atomic_t count; /*该结构的使用计数。当一个proc_dir_entry结构的count减为零时,会释放该结构*/ int deleted; /*这是一个删除标志,当我们调用remove_proc_entry函数要删除一个proc_dir_entry时,如果发现该结构还在使用,就会设置该标志并且退出。 */ kdev_t rdev; /* read_proc_t *read_proc 和write_proc_t *write_proc:这两个函数提供了对proc文件进行操作的简单接口。我们知道,对于proc文件,我们可以从中读取核心数据,还可以向其中写入数据,因此,对于一些功能比较简单的proc文件,我们只要实现这两个函数(或其中之一)即可,而不用设置inode_operations结构,这样,整个操作比较简单。实际上,我们会在后面的分析中看到,在注册proc文件的时候,会自动为proc_fops设置一个缺省的file_operations结构,如果我们只实现了上面提到的两个读写操作,而没有设置自己file_operations结构,那么,会由缺省的inode_operations结构中的读写函数检查调用这两个函数。*/ }; 5.函数create_proc_entry,由它创建并注册proc文件。struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode, struct proc_dir_entry *parent) {…… }/*函数内容不必详细了解,只要会调用此函数即可。name为要创建的文件名,mode为创建文件的属性;parent指向该文件父目录的指针,如果创建的虚拟文件位于/proc下,则这个参数为NULL(表示 /proc 根目录)。*/ 6. read_proc_t: 如果只是创建了虚拟文件,那么它并不能被读写。为此,我们必须为每个虚拟文件挂接读写函数,因为此题中只用到读函数,所以只看了函数指针read_proc字段的相关内容。类型定义如下: typedef int (read_proc_t)(char *page, char **start, off_t off, int count, int *eof, void *data); 当用户读创建的虚拟文件时,该文件对应的read_proc函数将被调用。该函数将数据写入内核的缓冲区page中。page 参数是数据写入到的位置,其中 count 定义了可以写入的最大字符数。在返回多页数据(通常一页是 4KB)时,需要使用 start 和 off 参数。当所有数据全部写入之后,就需要设置 eof(文件结束参数)。data 表示的是私有数据。page 缓冲区在内核空间中。 7.删除proc文件 void remove_proc_entry(const char *name, struct proc_dir_entry *parent) { ………} 该函数在参数parent的所有孩子中查找指定的名字name,如果找到匹配的节点,那么,就将该结构从树结构中去掉。然后,如果删除的proc_dir_entry是目录结构,那么,就减少其父节点的链接数。 8. sprintf函数: int sprintf( char *buffer, const char *format [, argument] ... ); 除了前两个参数类型固定外,后面可以接任意多个参数。而它的精华,就在第二个参数: 格式化字符串上。;返回值:字符串长度。 输出格式:例如sprintf(s, "%8d", 123); //产生:" 123 ",宽度占8 个位置,右对齐。 五.整体思路: 内核的输出进到了内核缓冲区中, /proc 目录下的所有信息都是动态的从正在运行的内核中读取,在这里使用proc文件系统显示内核内容,即用proc文件系统实现内核模块与用户交互。用户空间使用shell脚本。(因为想练一下shell脚本,所以用户空间没有用一般函数。) 六.程序代码 #include<linux/init.h> #include<linux/module.h> #include<linux/sched.h> #include<linux/list.h> #include<linux/proc_fs.h> MODULE_LICENSE("Dual BSD/GPL"); static struct proc_dir_entry *myfile; static int mod_init_modtest(void); static void mod_exit_modtest(void); module_init(mod_init_modtest); module_exit(mod_exit_modtest); static int proc_read_nullparam(char *page,char **start,off_t off,int count,int *eof,void *data) { int record=0; struct task_struct *p; for_each_process(p){ record=record+sprintf(page+record,"name:%20s pid:%8d state:%8ld\n",p->comm,p->pid,p->state); printk(KERN_INFO"sssssssssssssssssssssssssssssssssssfffffffffffffffffffffffffffffffffffssssssssssssssssssss%s\n",p->comm);//测试时用到的 } return record; } int mod_init_modtest(void) { myfile=create_proc_entry("nullparam",0500,NULL); myfile->read_proc=proc_read_nullparam; return 0; } void mod_exit_modtest(void) { remove_proc_entry("nullparam",NULL); printk(KERN_INFO"-----------Module_export_symbol was deleted!-------\n"); } MODULE_AUTHOR("CHEN MENG YUAN"); MODULE_DESCRIPTION("module1:Module_export_symbol --sum_op--fac_op--"); MODULE_VERSION("Ver 2.6.35"); #include<linux/init.h> #include<linux/module.h> #include<linux/sched.h> #include<linux/list.h> #include<linux/proc_fs.h> MODULE_LICENSE("Dual BSD/GPL"); static struct proc_dir_entry *myfile; static int mod_init_modtest(void); static void mod_exit_modtest(void); module_init(mod_init_modtest); module_exit(mod_exit_modtest); //static struct task_struct *p,*allp;cannot definit here static int pid=0; module_param(pid,int,S_IRUGO); static int proc_read_pidparam(char *page,char **start,off_t off,int count,int *eof,void *data) { int record=0; struct task_struct *p,*tmp1,*tmp2; for_each_process(p) { if(p->pid==pid){ record=record + sprintf(page+record,"my name:%15s pid:%8d\n",p->comm,p->pid); record=record + sprintf(page+record,"Parent name:%15s pid:%8d\n",p->parent->comm,p->parent->pid); list_for_each_entry(tmp1,&(p->children),sibling) { record=record + sprintf(page+record,"children name:%15s pid:%8d\n",tmp1->comm,tmp1->pid); } list_for_each_entry(tmp2,&(p->real_parent->children),sibling) { record=record + sprintf(page+record,"brother name:%15s pid:%8d\n",tmp2->comm,tmp2->pid); } } } return record; } int mod_init_modtest(void) { myfile=create_proc_entry("pidparam3",0500,NULL); myfile->read_proc=proc_read_pidparam; return 0; } void mod_exit_modtest(void) { remove_proc_entry("pidparam3",NULL); printk(KERN_INFO"-----------Module_export_symbol was deleted!-------\n"); } MODULE_AUTHOR("CHEN MENG YUAN"); MODULE_DESCRIPTION("module1:Module_export_symbol --sum_op--fac_op--"); MODULE_VERSION("Ver 2.6.35"); 七.遇到的问题 当时编程时进程状态直接调用task_struct的state字段,没有进行深入研究,老师检查完,我查了一下state的相关知识。现现在列出一部分。 1. 进程状态(State) 进程执行时,它会根据具体情况改变状态。进程状态是调度和对换的依据。Linux中的进程主要有如下状态,如表4.1所示。 表4.1 Linux进程的状态 内核表示 含义 TASK_RUNNING 可运行 TASK_INTERRUPTIBLE 可中断的等待状态 TASK_UNINTERRUPTIBLE 不可中断的等待状态 TASK_ZOMBIE 僵死 TASK_STOPPED 暂停 TASK_SWAPPING 换入/换出 ·可运行状态 处于这种状态的进程,要么正在运行、要么正准备运行。正在运行的进程就是当前进程(由current所指向的进程),而准备运行的进程只要得到CPU就可以立即投入运行,CPU是这些进程唯一等待的系统资源。系统中有一个运行队列(run_queue),用来容纳所有处于可运行状态的进程,调度程序执行时,从中选择一个进程投入运行。在后面我们讨论进程调度的时候,可以看到运行队列的作用。当前运行进程一直处于该队列中,也就是说,current总是指向运行队列中的某个元素,只是具体指向谁由调度程序决定。 ·等待状态 处于该状态的进程正在等待某个事件(event)或某个资源,它肯定位于系统中的某个等待队列(wait_queue)中。Linux中处于等待状态的进程分为两种:可中断的等待状态和不可中断的等待状态。处于可中断等待态的进程可以被信号唤醒,如果收到信号,该进程就从等待状态进入可运行状态,并且加入到运行队列中,等待被调度;而处于不可中断等待态的进程是因为硬件环境不能满足而等待,例如等待特定的系统资源,它任何情况下都不能被打断,只能用特定的方式来唤醒它,例如唤醒函数wake_up()等。 ·暂停状态 此时的进程暂时停止运行来接受某种特殊处理。通常当进程接收到SIGSTOP、SIGTSTP、SIGTTIN或 SIGTTOU信号后就处于这种状态。例如,正接受调试的进程就处于这种状态。 ·僵死状态 进程虽然已经终止,但由于某种原因,父进程还没有执行wait()系统调用,终止进程的信息也还没有回收。顾名思义,处于该状态的进程就是死进程,这种进程实际上是系统中的垃圾,必须进行相应处理以释放其占用的资源。 Linux内核中对进程状态进行了如下宏定义。 #define TASK_RUNNING 0 #define TASK_INTERRUPTIBLE 1 #define TASK_UNINTERRUPTIBLE 2 #define TASK_ZOMBIE 4 #define TASK_STOPPED 8 在我的测试结果中,几乎都都是1号状态,即可中断的等待状态。 八.测试结果 改进输出格式后的测试结果: 查看内核版本信息 九.总结 通过此门课程设计真的学到了很多东西,linux学习只看课本不行,要进行linux环境下的实践才能真正掌握好linux,在课程设计过程中发现很多东西不会,一边查资料问同学,一边尝试,当有结果出来时真的很高兴,激发了我学习linux的兴趣。 课程设计完成了,不能说我很好的掌握了内核模块编程与proc文件系统,但自己感觉比以前是进步了,还是很高兴。 15
展开阅读全文

开通  VIP会员、SVIP会员  优惠大
下载10份以上建议开通VIP会员
下载20份以上建议开通SVIP会员


开通VIP      成为共赢上传
相似文档                                   自信AI助手自信AI助手

当前位置:首页 > 学术论文 > 其他

移动网页_全站_页脚广告1

关于我们      便捷服务       自信AI       AI导航        抽奖活动

©2010-2025 宁波自信网络信息技术有限公司  版权所有

客服电话:4009-655-100  投诉/维权电话:18658249818

gongan.png浙公网安备33021202000488号   

icp.png浙ICP备2021020529号-1  |  浙B2-20240490  

关注我们 :微信公众号    抖音    微博    LOFTER 

客服