我是靠谱客的博主 疯狂悟空,最近开发中收集的这篇文章主要介绍Linux内核数据结构,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

Linux内核数据结构

1.内核的数据类型:bool:布尔类型 inbool:布尔反值charp:字符指针

short:短整型 ushort:int:整形 uintlong ulong:

2.内核编程不允许处理浮点数:cpu处理浮点型数据的效率极低---用户空间处理浮点型数据

如果对内核模块的某一个变量进行修改,必须要声明:

内核模块参数;module_param(name,tyoe,perm);

功能:指定模块参数,用于在加载模块的时候或者模块加载以后传递参数给模块。

name:模块参数的名称 type:模块参数的数据类型,无浮点数

perm:模块参数的访问权限,如果权限非0,每当加载完模块以后,会在/sys/module目录下会生成一个跟模块名同名的目录,在这个目录下有一个parameters,在这个目录下会生成一个跟变量名同名的文件,以后通过修改这个文件,来进行修改这.

int irq;

charp *pstr;

module_param(irq,int,0664);

module_param(pstr,charp,0);  //内核不允许可执行

irq--/sys/module/hellokernel/parmeters/irq

psr--/sys/module/hellokernel/parameters/no

权限值为非零,将会存在一个文件,为0不会存在文件

insmod helloworld.ko irq=100 pstr=china

**********************************************************

3.内核模块参数声明:

         作用:像应用程序命令行参数一样,给程序传递参数信息./a.out 100 200内核如果也需要给程序传递参数,需要使用内核模块参数声明来解决。

实验步骤:

1.不传参数insmod helloworld.ko lsmod  rmmodhelloworld

2.传参数 insmod helloworld。ko irq=100pstr=tarena lsmod rmmod helloworld

3.修改文件来修改变量的内容insmod helloworld.ko irq=100 pstr=tarena lsmod

ls /sys/module/helloworld/parameters/ cat   /sys/module/helloworld/parameters/ //读文件

echo 2000 >/sys/module/helloworld/parameters/irq

//修改文件内容 rmmod helloworld //查看irq是否修改为2000

案例:把pstr的权限也给0664

***************************************************

4.内核模块参数数组 module_param_arry(name,type,nump,perm)

功能:指定模块参数,用于加载模块时或者模块加载以后传递参数模块

name:模块参数的名称 type:模块参数的数据类型,无浮点数

nump:数组元素个数指针 perm:模块参数的访问权限

fish----//sys/module/hellokernel/parameters/fish

总结;虽然在进行模块参数声明的时候,如果给定l权限,那么就会在指定目录下生成一个跟变量名同名的文件,修改文件既可以对变量进行修改,这个方式虽然比较方便,灵活,但是这个空间存在于内存中,如果驱动有大量的这样代码,会造成内存的大量使用!如果没有需要在模块运行行期间改变变量内容的需求,权限一律给0!如果权限为0,

只能自insmod进行修改!!

**************************************

5.内核模块符号导出

EXPORT_SYMBOL(函数名或者变量名)EXPORT_SYMBOL_GPL(函数名或者变量名)

功能:导出函数或者变量供其他模块使用;后者只能用于包含GPL许可证的模块

MODULE_LICENSE("GPL"); //声明遵循gpl协议

案例:helloworld.c调用test_module.c的某个函数

调试宏:_FUNCTON_,_FUNC_,%S _LINE_,%D_FILE_,%S _DATE_,%S _TIME_,%S

6.内核多文件编译

Makefile 分别编译 obj-m +=a.o b.o

编译在一起: obj-m +=C.o //不能是a.o或者b.oC-objs =A.o B.o

多文件模块的加载:一定首先加载依赖模块,例如a.ko调用B.ko 某个函数,那么a依赖b。现价在insmod B.ko ,后加载insmod a.ko.卸载时,先卸载a,后卸载依赖模块b。

总结:写任何.c文件,都要加MODULE_LICENSE("GPL");

modprobe 加载模块和卸载模块/lib/modules/($version)/modules.dep //模块关系依赖文件

8.模块的安装:make modules_install INSTALL_MOD_PATH=/home/tarena

1.在/home/tarena 下生成lib目录2.将lib下所有内容拷贝到开发板根文件系统的lib目录中

9.linux修改文件所属用户和组使用chown命令可以修改文件或目录所属的用户:

?????? 命令:chown 用户 目录或文件名

?????? 例如:chown qq /home/qq? (把home目录下的qq目录的拥有者改为qq用户)?

使用chgrp命令可以修改文件或目录所属的组:

?????? 命令:chgrp?组 目录或文件名

?????? 例如:chgrp?qq /home/qq? (把home目录下的qq目录的所属组改为qq组)

*****************************************

10.如果模块之间的依赖过于复杂,可以利用modprobe命令来加载或者卸载模块.例如a.ko依赖b.ko,每当modprobe a.ko时,此命令根据modules.dep,先加载b.ko,后再加载a.ko,此时可以运行。

modprobe命令使用步骤:1.进入内核源码cd /opt/kernel makemodules

回到 home/tarena/drivers 驱动程序开发的位置

2.修改Makefile,添加如下信息

install:

         make-C$(KDIR) SUBDIRS=$(PWD)

modules_install INSTALL_MOD_PATH=/opt/lib

#modules_install表示安装模块,主要是产生modules.dep依赖关系文件,然后将.ko拷贝到extra目录下,

#INSTALL_MOD_PATH表示安装到哪个目录下

#一旦执行make install安装模块时,最后在/opt目录下生成一个lib目录,然后将lib目录的内容拷贝到/opt/rootfs/lib下

3.make //编译模块 4.make install //安装模块 5.cp/opt/lib/* /opt/rootfs/lib/ -frd

6.在开发板上进行加载:modprobe helloworld.ko / modprobe helloworld精简版本

说明:modprobe默认到/lib/modules/2.6..../下找依赖文件modules.dep,

根据依赖文件,决定先加载哪个模块!模块在/lib/modules/2.6.../extra

卸载:modprobe -rhelloworld //卸载模块时,先卸载test.ko 再卸载helloworld.ko

modinfo helloworld.ko modinfo: can't open'/lib/modules/2.6.35.7-Concenwit/modules.dep':

No such file or directory 能够解决了,因为在/lib/modules/2.6../extra 相应的modules.dep文件

# modinfo helloworld.ko filename:      helloworld.ko/ # //可以加载成功

//在写驱动模块的时候可以加上相应的驱动模块信息,想作者或者遵循协议gpl

 

11.内核打印prinfk vsprintf

内核打印函数printk用法:1.printfk能够指定输出打印级别(0-7)

2.printfk打印输出信息要依赖默认的输出级别,printfk函数在使用时,指定的输出级别如果小于默认的级别,信息一律打印,否则不会打印。

3.设置默认的输出级别的方法:方法一:通过配置文件cat /proc/sys/kernel/printk

 关注第一个值echo 8 > /proc/sys/kernel/prnitk缺点:无法设置内核启动的打印信息

方法2.通过修改uboot的bootargs来实现,这种设置方法能够内核启动时的打印输出

setenv bootargs root=/dev/nfs ........quiet4//设置默认的输出级别 boot //启动

setenv bootargs root=/dev/nfs ........debug10//设置默认的输出级别boot //启动

setenv bootargs root=/dev/nfs........loglevel=数字 boot //启动

内核打印函数优先级

#define KERN_EMERG   "<0>"  通常是系统崩溃前的消息

#define KERN_ALERT   "<1>"  需啊立即处理的消息

#define KERN_CRIT    "<2>"  严重情况

#define KERN_ERR     "<3>"  错误信息

#define KERN_WARNING"<4>"  有问题的情况

#define KERN_NOTICE  "<5>"  正常情况,但任然注意

#define KERN_INFO    "<6>"  信息型消息

#define KERN_DEBUG   "<7>"  用作调试信息

数字越大,消息的级别越低!内核里面有的人使用宏有的使用字符串 printfk后解析《

内核默认的级别,如果输出信息高于默认级别就不会打印,

例如:printfk("<0>""level 0n");

总结:产品最终发布时,打印信息一律屏蔽!产品软件测试阶段,使用debug来查看程序的运行状态

12linux内核链表相关内容。

1.      内核链表与普通链表的比较:(1)内核链表结构体不包含数据域,只包含维护链表的指针域。(2)内核链表具有通用性,和具体数据结构无关。(3)内核链表常被用作双向循环链表(4)内核链表被包含在其它数据结构体中使用。

2.      内核链表的主要操作(1)初始化链表头INIT_LIST_HEAD函数 (2)插入节点list_add函数(3)删除节点list_del函数(4)提取数据结构list_entry宏(5)遍历链表list_for_each宏以上函数的参数只接受list_head,不关心具体的数据结构。

3.传统链表的特点:

struct fox{

         inta;

         intb;

         structfor *next;

         structfox *prev;

};1.传统链表的指针域的数据类型和节点的数据类型保持一致!

2.传统链表的指针域永远指向下一个节点的首地址或者前一个字节的首地址!传统链表头一般都是拿首节点作为链表头!

注意:不管什么链表,一般链表都要进行初始化,插入,删除,遍历,合并等操作。

缺点:由于传统链表的指针域和节点的数据类型保持一致,并且链表的操作都需要插入,删除和遍历,那么都需要定义一组操作函数,那么节点的数据类型不一样,最终导致不同的链表都有自己独立的操作函数,例如现在有三个链表:struct foc,struct dag,struct cat ,那么最终以上三个链表都有自己独立的三个操作函数,最终导致代码量相当的庞大和难以维护!

总结:链表的操作关键操作指针域!

linux内核链表的结构:

struct list_head{

         structlist_head *next, *prev;

};

1.内核链表的指针域和节点的数据类型不必一致内核链表的指针域只跟struct list———head相关;

2.内核链表的指针域永远指向下一个或者前一个节点的指针域。,不在指向节点的首地址;

3.内核链表头一般使用struct list_head来表示,内核链表头进行只包含指针域,不包括数据域!.内核链表具有通用性,因为链表操作都是通过指针域来进行,所以既然内核链表的指针域都跟struct list_head相关,所以内核的双链表的操作只需要一组函数即可!这些函数操作方法都是定义在内核源码的include/linux/list.h。

4.内核链表使用规范:通过表头获取的是结构体中list_head成员地址,如果获取结构体的首地址需要通过container_of宏。

 

linux内核链表的使用:#include <linux/list.h>

1.定义链表头struct list_head fox_head;

2.初始化链表头INIT_LIST_HEAD(&fox_head);或者1+2:LIST_HEAD(for_head);

3.声明描述实物的结构体,然后将struct list_head嵌入这个结构体中:

struct fox{

         inta;

         intb;

         structlist_head list;

};

4.分配节点用户空间:malloc

内核空间:kzlloc=kmalloc +memset();

struct fox *fox1 = kzalloc(sizeof(structfor),GFP_KERNEL);

struct fox *fox2 = kzalloc(sizeof(structfor),GFP_KERNEL);

struct fox *fox3 = kzalloc(sizeof(structfor),GFP_KERNEL);

5.节点的插入操作list_add(struct list_head *new, struct list_head *head);

功能:插入一个新的节点到链表头中new:新节点的指针域的首地址head:链表头的首地址

例如:list_add(&fox1->list,&fox_head);list_add(&fox2->list, &fox_head);

list_add(&fox3->list,&fox_head);添加以后的顺序:链表头->fox3->fox2->fox1

 

list_add_tail(struct list_head *new, structlist_head *head);

new:新节点的指针域的首地址head:链表头的首地址

例如:list_add(&fox1->list,&fox_head);list_add(&fox2->list, &fox_head);

list_add(&fox3->list,&fox_head); 添加以后的顺序:链表头->fox1->fox2->fox3

6.删除链表节点list_del(struct list_head *new);

功能:删除节点new:要删除节点的指针域的首地址

list_del(&fox1->list);list_del(&fox2->list);list_del(&fox3->list);

7.遍历链表

#define list_for_each(pos,head)

         for(pos=(head)->next);

                   pos!=(head);

                   pos=pos->next)

功能:遍历链表,本质上是一个for循环pos:保存每一个节点的指针域的首地址

head:链表头

问题:pos是保存节点的指针域的首地址,如何访问这个节点的数据域呢?

8.通过节点的指针域的首地址获取节点的首地址,从而访问节点的数据域

#define list_entry(ptr,type,member)

         container_of(ptr,type,member)

container_of使用:

功能:如果已知结构体某一个成员的地址,通过这个宏能够获取结构体的首地址;

ptr:已知的成员地址,pos=&fox1->list type:结构体名,struct fox

member:成员的变量名list 返回值:就是结构体的首地址

例如:struct fox *fox=container_of(pos,struct fox,list);

访问数据域:fox->a,fox->b

#define container_of(ptr,, type, member) ({

const typeof(((type*)0) - >member )* __mptr = (ptr);

(type *) ( (char *)__mptr - offsetof(type,member)

); })

 

#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)

案例:在用户空间统计3个狐狸

案例:在内核空间统计5个员工

 

 

最后

以上就是疯狂悟空为你收集整理的Linux内核数据结构的全部内容,希望文章能够帮你解决Linux内核数据结构所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(46)

评论列表共有 0 条评论

立即
投稿
返回
顶部