[Linux服务器] Linux内核

380 0
Honkers 2026-6-6 00:39:00 来自手机 | 显示全部楼层 |阅读模式

linux内核源代码位于/usr/src/linux目录下,主要分布结构是:


文件描述:

  • include/子目录包含的是建立内核代码时所需的大部分包含文件
  • init/子目录包含了内核的初始化代码,内核起始点
  • arch/子目录包含了Linux支持的所有硬件结构的内核代码
  • drivers/子目录包含了内核中所有的设备驱动程序,例如字符设备、块设备、scsi设备驱动程序等
  • fs/子目录包含了所有文件系统的代码
  • net/子目录包含了内核中关于网络的代码
  • mm/子目录包含了所有的内存管理代码
  • ipc/子目录包含了进程间通信的代码
  • kernel/子目录包含了主内核代码

Linux内核模块编程

  1. 编写模块
  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/init.h>
  4. static int_init lkp_init(void)
  5. {
  6. printk("<1>Hello,World!from the kernel space...\n");
  7. return 0;
  8. }
  9. static void_exit lkp_cleanup(void)
  10. {
  11. printk("<1>Goodbye,Word!leaving kernel space...\n)";
  12. }
  13. modele_init(lkp_init;)
  14. mode_exir(lkp_cleanup);
  15. MODELE_LICESEN("GPL");
复制代码
  1. 说明:
    1.
    • module.h头文件中包含对模块的结构定义以及模块的版本控制,任何模块程序的编写都要包含这个头文件;
    • 头文件kernel.h包含了常用的内核函数;
    • 而头文件init.h包含了宏_init和_exit,宏_init告诉编译器程序相关的函数和变量仅用于初始化,编译程序将标有_init的所有代码存储到特殊的内存段中,初始化结束后就释放这段内存。
    1. 函数lkp_init()是模块初始化函数,函数lkp_cleanp()是模块的推出和清理函数。
    2. printk()函数由内核定义,与C库中的printf()类似。字符串<1>是输出的级别,表示立即在终端输出。
    3. module_init()和cleanup_exit()是模块编程中最基本也是最必须的两个函数。module_init()向内核注册模块提供新功能,cleanup_exit()注销由模块所提供的功能。
    4. 最后一句是告诉内核该模块具有GNU公开许可证。
  2. 编译模块

假定程序名为hellomod.c,只有超级用户才能加载和卸载模块,其Makefile文件基本内容如下

  1. #Makefile2.6
  2. obj-m:=hellomod.o #产生hellomod模块的目标文件
  3. CURRENT_PATH:=$(shell pwd) #模块所在的当前路径
  4. LINUX_KERNEL:=$(shell uname -r) #Linux内核源代码的当前版本
  5. LINUX_KERNEL_PATH:= /usr/src/linux-headers-$(LINUX_KERNEL) #Linux内核源代码的绝对路径
  6. all:
  7. make -C $(LIN UX_KERNEL_PATH) M=$(CURRENT_PATH) modules #编译模块
  8. clean:
  9. make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean #清理
复制代码

obj-m :=赋值语句含义:要使用目标文件hellomod.o建立一个模块,最后生成hellomod.ko,如果有一个名为module.ko的模块依赖于两个文件file.o和file2.o,则可以使用module-obj扩展,如下:

  1. obj-m : = module.o
  2. module-objs : = file.o file2.o
复制代码

make命令运行Makefile。

  1. 运行模块
    1. 当编译好模块,要将编译好的模块插入到内核中时可以使用insmod命令实现。
      • 例:insmod hellomod.ko
    2. lsmod命令可以用来检查模块是否正确插入到了内核中。
    3. 模块的输出由prink()来产生,该函数默认打印系统文件
      • /var/log/messages
    4. 快速浏览该信息可以输入:
      • tail/var/log/messages(打印日志文件的最后10行内容)
    5. 使用rmmod命令,加上insmod中看到的模块名,可以从内核中移除该模块(还可以看到退出时显示的信息)
  2. 应用程序和内核比较
    | 比较内容 | C语言应用程序 | 内核模块程序 |
    | — | — | — |
    | 使用函数 | libc库 | 内核函数 |
    | 运行空间 | 用户空间 | 内核空间 |
    | 运行权限 | 普通用户 | 超级用户 |
    | 入口函数 | main() | module_init() |
    | 出口函数 | exit() | module_exit() |
    | 编译 | gcc -c | make |
    | 连接 | gcc | insmod |
    | 运行 | 直接运行 | insmod |
    | 调试 | gdb | kdbug,kdb,kgdb等 |

Linux内核中链表的实现和应用

1.链表的演化

  1. struct my_list{
  2. void *mydata;
  3. struct my_list *next;
  4. struct my_list *prev;
  5. };
复制代码

如图所示,一个双链表,通过前驱(Prev)和后继(Next)两个指针域,可以从两个方向遍历双链表,使得遍历链表的代价减少。若打乱前驱和后继的依赖关系,就可以构成“二叉树”;如果再让首结点的前驱指向链表的尾结点、尾结点的后继指向首结点,就构成了循环链表;如果设计更多的指针域,就可以构成负责的树状结构。



如果减少一个指针域,就退化成单链表,如果只能对链表的首位进行插入或删除操作,就演变成了队列结构,如果对链表的头进行插入或删除操作就退化为栈结构。

2.链表的定义和操作

  1. 定义
  1. //不包含数据域的链表
  2. struct list_head{
  3. struct list_head *next,*prev;
  4. }
  5. //包含数据域的链表
  6. struct my_list{
  7. void * mydata;
  8. struct list_head list;
  9. };
复制代码
  1. 1. list域隐藏了链表的指针特性
  2. 1. struct list_head 可以位于结构的任何位置,可以给其任意名字
  3. 1. 在一个结构中可以有多个list域
复制代码

以struct list_head为基本对象,对链表进行插入、删除、合并以及遍历操作。

  1. 声明和初始化宏

链表结构的建立首先需要定义两个宏定义:

  1. #define LIST_HEAD_INIT(name){&(name),&{name}}/*仅初始化*/
  2. #define LIST_HEAD(name)struct_list_head name = LIST_HEAD_INIT(name)/*声明并初始化*/
复制代码

如果要申明并初始化自己的链表头的mylist_head,则直接调用LIST_HEAD:

  1. LIST_HEAD(mylist_head)
复制代码

调用之后,mylist_head的next、prev指针都初始化为指向自己,这样就有了一个空链表,如何判断链表是否为空,就是让头指针的next指向自己。

  1. 链表中增加节点

list.h中增加节点的函数为:

  1. static inline void list_add();
  2. static inline void list_add_tail();
复制代码

在内核代码中,函数名前加两个下划线表示内部函数,第一个函数的具体代码如下:

  1. static inline void _list_add(struct list_head *new,
  2. struct list_head *prev,
  3. struct list_head *next)
  4. {
  5. next->prev = new;
  6. new->next = next;
  7. new->prev =prev;
  8. prev->next = new;
  9. }
复制代码

调用这个内部函数已分别在链表头和尾增加节点:

  1. static inline void list_add(struct list_head * new,struct list_head * head)
  2. {
  3. _list_add(new,head,head->next);
  4. }
复制代码

该函数向指定链表的head结点后插入new结点,因为链表是循环的,而通常没有首位结点的概念,所以可以将任意结点传递给head。但如果传递最后一个元素给head,则该函数可以实现一个栈。

  1. static inline void list_tail(struct list_head *new,struct list_head *head)
  2. {
  3. _list_add(new,head->prev,head);
  4. }
复制代码

该函数向指定链表的head结点前插入new结点。和list_add()函数类似,因链表是环形的,且可以将任何结点传递给head。但如果传递第一个元素给head,则该函数可以用老实现一个队列。
在函数名前加static inline关键字说明,static加在函数前,表示这个函数是静态函数,对该函数作用域的限制,指该函数的作用域仅限于本文件。static具有隐藏作用。关键字inline加载函数前说明该函数对编译程序是可见的,编译程序在调用这个函数时就立即展开该函数。所以关键字inline必须与函数定义体放在一起才能使函数成为内联。inline函数一般放在头文件中。

  1. 链表遍历

list.h中定义如何遍历链表的宏:

  1. #define list_for_each(pos,head)
  2. for(pos = (head)->next;pos!=(head);
  3. pos = pos->next)
复制代码

这种遍历仅仅是找到一个个结点在链表中的偏移位置pos,如图1.7。

为了获取pos结点的起始地址,从而引用结点中的域,在list.h中定义list_entry()宏:

  1. #define list_entry(ptr,type,member)
  2. ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
复制代码

指针ptr指向结构体type中的成员member;通过指针ptr,返回结构体type的起始地址,也就是list_entry返回指向type类型的指针,如图1.8。



((unsigned long)(&((type *)0)->member)把0地址转化为type结构的指针,然后获取该结构中member域的指针,也就是获得member在type结构中的偏移量。其中(char *)(ptr)求出的是ptr的绝对地址,二者相减,于是获得type类型结构体的起始地址,如图1.9。

  1. 链表的应用

Linux内核模块,用以创建、增加、删除和遍历一个双向链表

  1. #include <linux/kernel.h>
  2. #include <linux/module.h>
  3. #include <linux/slab.h>
  4. #include <linux/list.h>
  5. MODULE_LICENSE("GPL");
  6. MODULE_AUTHOR("XIYOU");
  7. #define N 10 //链表结点数
  8. static inline void list_tail(struct list_head *new,struct list_head *head)
  9. {
  10. _list_add(new,head->prev,head);
  11. }
  12. static inline void _list_add(struct list_head *new,
  13. struct list_head *prev,
  14. struct list_head *next)
  15. {
  16. next->prev = new;
  17. new->next = next;
  18. new->prev =prev;
  19. prev->next = new;
  20. }
  21. struct list_head{
  22. int *next;
  23. int *prev;
  24. }
  25. struct numlist{
  26. int num; //数据
  27. srtuct list_head list; //指向双链表前后结点的指针
  28. };
  29. struct int _init doublelist_init(void)
  30. {
  31. //初始化结点
  32. struct numlist *listnode; //每次申请链表结点时所用的指针
  33. struct list_head *pos;
  34. struct numlist *p;
  35. int i;
  36. printk("doublelist is starting...\n");
  37. INIT_LIST_HEAD(&numble.list);
  38. //建立N个结点,依次加入到链表当中
  39. for(i = 0;i<N;i++)
  40. {
  41. //kmalloc()在内核空间申请内存,类似于malloc()
  42. listnode = (struct numlist *)kmalloc(sizeof(struct numlist),GFP_KERNEL);
  43. listnode -> num = i+1;
  44. list_add_tail(&listnode -> list,&numhead.list);
  45. printk("Node %d has added to the doublelist...\n",i+1);
  46. }
  47. //遍历链表
  48. i=1;
  49. list_for_each(pos,&numhead.list)
  50. {
  51. p = list_entry(pos,struct numlist,list);
  52. printk("Node %d`s data: %d\n",i,p->num);
  53. i++;
  54. }
  55. return 0;
  56. }
  57. ststic void _exit doublelist_exit(void)
  58. {
  59. struct list_head *pos,*n;
  60. struct numlist *p;
  61. int i;
  62. //依次删除N个结点
  63. i= 1;
  64. list_for_each_safe(pos,n,&numhead.list)
  65. {
  66. list_del(pos);
  67. p = list_entry(pos,struct numlist,list);
  68. kfree(p);
  69. printk("Node %d has renmove from the doublelist...\n",i++);
  70. }
  71. printk("doublelist is exiting..\n");
  72. }
  73. module_init(doublelist_init);
  74. module_exit(doublelist_exit);
  75. static inline void _list_del(struct list_head * prev,struct list_head *next)
  76. {
  77. next->prev = prev;
  78. prev->next = next;
  79. }
  80. static inline void list_del(struct list_head *entry)
  81. {
  82. _list_del(entry->prev,entry->next);
  83. entry->next = LIST_POISON1;
  84. entry->prev = LIST_POISON2;
  85. }
复制代码

当执行删除操作时,被删除的结点两个指针被指向一个固定的位置(LIST_POISON1和LIST_POISON2是内核空间的两个地址)。而list_for_each(pos,head)中的pos指针在遍历过程中向后移动,即pos = pos->next,如果执行了list_del()操作,pos将指向这个固定位置的next,prev,而此时的next,prev没有任何指向,必然出错。
而list_for_each_safe(p,n,head)宏解决了该问题:

  1. #define list_for_each_safe(pos,n,head)
  2. for(pos = (head) -> next,n=pos->next;pos!=(head);
  3. pos = n,n = pos->next)
复制代码

采用同类型的指针n来暂存将要被删除的结点指针pos,从而使得删除操作不影响pos指针。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

中国红客联盟公众号

联系站长QQ:5520533

admin@chnhonker.com
Copyright © 2001-2026 Discuz Team. Powered by Discuz! X3.5 ( 粤ICP备13060014号 )|天天打卡 本站已运行