linux kernel 学习手记4 内核中的数据类型

十五.linux开发之Kernel移植——话说linux内核 有道云笔记地址: 详情看这里链接,记录太多,就不一一排版了。 http://note.youdao.com/noteshare?id=7cc3e91b91754d1ba42913229eb18689&sub=E4714EDA91904A59AF9460249A4E1B6F   一、Linux Kernel简介   (1)linux、windows、android、ucos就... 阅读详情

 11  内核中的数据类型

坚持严格的数据类型和使用 -Wall -Wstrict-prototypes 进行编译可能避免大部分的 bug.

内核数据使用的数据类型分为 3 个主要类型:

1.       标准 C 类型例如 int,

2.       明确大小的类型例如 u32,

3.       以及用作特定内核对象的类型, 例如 pid_t.

内核中通常的内存地址常常是 unsigned long, 利用了指针和长整型一直是相同大小的这个事实

所有的数据声明在 <asm/types.h>, 它又被 <linux/types.h> 包含.

u8; /* unsigned byte (8 bits) */

u16; /* unsigned word (16 bits) */

u32; /* unsigned 32-bit value */

u64; /* unsigned 64-bit value */

如果一个用户空间程序需要使用这些类型, 可用使用一个双下划线前缀在名子上: __u8 和其它独立于 __KERNEL__ 定义的类型. 例如, 如果, 一个驱动需要与用户空间中运行的程序交换二进制结构, 通过 ioctl, 头文件应当在结构中声明 32-位 成员为 __u32.

重要的是记住这些类型是 Linux 特定的, 并且使用它们妨碍了移植软件到其他的 Unix 口味上. 使用近期编译器的系统支持 C99-标准 类型, 例如 uint8_t uint32_t; 如果考虑到移植性, 使用这些类型比 Linux-特定的变体要好.

接口特定的类型

_t 类型在 <linux/types.h> 中定义,很多通过 typedef定义来隐藏实际数据类型,实现移殖性,但是这种用法不受开发者欢迎。

但是如果驱动程序使用了这些订制的类型的函数,但是有不遵守约定时候,编译器就会产生警告,可以使用-Wall编译选项来细心消除所有的警告确保可移植性。

printfprintk中使用了这些结构时候,最好先做一次类型强制转换,消除警告。

还有一条通用的原则就是避免显示使用常量,代码通过宏定义来避免这个问题,实现可移植性性和可读性。

时间间隔

使用宏HZ常见是msec毫秒对应的jiffies数目总是msec*HZ/1000

页大小

PAGE_SIZEPAGE_SHIFT是为了得到一个地址所在页的页号需要对该地址右移的位数。

如果一个驱动需要 16 KB 来暂存数据, 它不应当指定一个 2 的指数 给 get_free_pages. 你需要一个可移植解决方法. 这样的解决方法, 幸运的是, 已经由内核开发者写好并且称为 get_order:

#include <asm/page.h>
int order = get_order(16*1024);
buf = get_free_pages(GFP_KERNEL, order);

记住, get_order 的参数必须是 2 的幂.

字节序

包含文件 <asm/byteorder.h> 定义了或者 __BIG_ENDIAN 或者 __LITTLE_ENDIAN, 依赖处理器的字节序. 当处理字节序问题时, 你可能编码一堆 #ifdef __LITTTLE_ENDIAN 条件语句, 但是有一个更好的方法. Linux 内核定义了一套宏定义来处理之间的转换, 在处理器字节序和你需要以特定字节序存储和加载的数据之间. 例如:

u32 cpu_to_le32 (u32);
u32 le32_to_cpu (u32);

此类的函数有十几个个在<linux/byteorder/big_endian.h> <linux/byteorder/little_endian.h> 中见到完整列表.

数据对齐

果你需要存取不对齐的数据, 你应当使用下列宏:

#include <asm/unaligned.h>
get_unaligned(ptr);
put_unaligned(val, ptr);

应当一直强制自然的数据项对齐, 加上对一个特定对齐方式的标准化. 自然对齐意味着存储数据项在是它的大小的整数倍的地址上(例如, 8-byte 项在 8 的整数倍的地址上). 为强制自然对齐在阻止编译器以不希望的方式安排成员量的时候, 你应当使用填充者成员来避免在数据结构中留下空洞.

要知道编译器可能自己悄悄地插入填充到结构中来保证每个成员是对齐的, 为了目标处理器的良好性能. 如果你定义一个结构打算来匹配一个设备期望的结构, 这个自动的填充可能妨碍你的企图. 解决这个问题的方法是告诉编译器这个结构必须是"紧凑的", 不能增加填充者. __attribute__ ((packed))如:内核头文件 <linux/edd.h> 定义几个与 x86 BIOS 接口的数据结构, 并且它包含下列的定义:

struct
{
        u16 id;
        u64 lun;
        u16 reserved1;
        u32 reserved2;
}
__attribute__ ((packed)) scsi;

指针和错误值

为帮助创建和使用这类接口, 一小部分函数已可用( <linux/err.h>).

一个返回指针类型的函数可以返回一个错误值, 使用:

void *ERR_PTR(long error);

这里, error 是常见的负值错误码. 调用者可用使用 IS_ERR 来测试是否一个返回的指针是不是一个错误码:

long IS_ERR(const void *ptr); 

如果你需要实际的错误码, 它可能被抽取到, 使用:

long PTR_ERR(const void *ptr); 

你应当只对 IS_ERR 返回一个真值的值使用 PTR_ERR; 任何其他的值是一个有效的指针.

链表

为使用列表机制, 你的驱动必须包含文件 <linux/list.h>. 这个文件定义了一个简单的类型 list_head 结构:

struct list_head { struct list_head *next, *prev; }; 

为在你的代码中使用 Linux 列表, 你只需要嵌入一个 list_head 在构成这个链表的结构里面.

链表头必须在使用前用 INIT_LIST_HEAD 宏来初始化. 
struct list_head todo_list; 
INIT_LIST_HEAD(&todo_list); 
还可以直接调用这个宏在编译时候初始化
LIST_HEAD(todo_list); 

 

几个使用链表的函数定义在 <linux/list.h>:

list_add(struct list_head *new, struct list_head *head);

在紧接着链表 head 后面增加新入口项 -- 正常地在链表的开头. 因此, 它可用来构建堆栈. 但是, 注意, head 不需要是链表名义上的头; 如果你传递一个 list_head 结构, 它在链表某处的中间, 新的项紧靠在它后面. 因为 Linux 链表是环形的, 链表的头通常和任何其他的项没有区别.

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

刚好在给定链表头前面增加一个新入口项 -- 在链表的尾部, 换句话说. list_add_tail 能够, 因此, 用来构建先入先出队列.

list_del(struct list_head *entry);

list_del_init(struct list_head *entry);

给定的项从队列中去除. 如果入口项可能注册在另外的链表中, 你应当使用 list_del_init, 它重新初始化这个链表指针.

list_move(struct list_head *entry, struct list_head *head);

list_move_tail(struct list_head *entry, struct list_head *head);

给定的入口项从它当前的链表里去除并且增加到 head 的开始. 为安放入口项在新链表的末尾, 使用 list_move_tail 代替.

list_empty(struct list_head *head);

如果给定链表是空, 返回一个非零值.

list_splice(struct list_head *list, struct list_head *head);

list 紧接在 head 之后来连接 2 个链表.

ist_head 结构对于实现一个相似结构的链表是好的, 但是调用程序常常感兴趣更大的结构, 它组成链表作为一个整体. 一个宏定义, list_entry, 映射一个 list_head 结构指针到一个指向包含它的结构的指针. 它如下被调用:

list_entry(struct list_head *ptr, type_of_struct, field_name); 

这里 ptr 是一个指向使用的 struct list_head 的指针, type_of_struct 是包含 ptr 的结构的类型, field_name 是结构中列表成员的名子. 在我们之前的 todo_struct 结构中, 链表成员称为简单列表. 因此, 我们应当转变一个列表入口项为它的包含结构, 使用这样一行:

struct todo_struct *todo_ptr = list_entry(listptr, struct todo_struct, list); 

 

 

struct todo_struct
{
        struct list_head list;
        int priority; /* driver specific */
        /* ... add other driver-specific fields */
};
struct todo_struct *todo_ptr = list_entry(listptr, struct todo_struct, list); 

 

 

为一个通用的规则, 最好使用一个预定义的宏来创建循环, 它遍历链表. 前一个循环, 例如, 可这样编码:

void todo_add_entry(struct todo_struct *new)
{
        struct list_head *ptr;
        struct todo_struct *entry;
 
        list_for_each(ptr, &todo_list)
        {
entry = list_entry(ptr, struct todo_struct, list);
                if (entry->priority < new->priority) {
 
                        list_add_tail(&new->list, ptr);
                        return;
                }
        }
        list_add_tail(&new->list, &todo_struct)
 
}

list_for_each(struct list_head *cursor, struct list_head *list)

这个宏创建一个 for 循环, 执行一次, cursor 指向链表中的每个连续的入口项. 小心改变列表在遍历它时.

list_for_each_prev(struct list_head *cursor, struct list_head *list)

这个版本后向遍历链表.

list_for_each_safe(struct list_head *cursor, struct list_head *next, struct list_head *list)

如果你的循环可能删除列表中的项, 使用这个版本. 它简单的存储列表 next 中下一个项, 在循环的开始, 因此如果 cursor 指向的入口项被删除, 它不会被搞乱.

list_for_each_entry(type *cursor, struct list_head *list, member)

list_for_each_entry_safe(type *cursor, type *next, struct list_head *list, member)

这些宏定义减轻了对一个包含给定结构类型的列表的处理. 这里, cursor 是一个指向包含数据类型的指针, member 是包含结构中 list_head 结构的名子. 有了这些宏, 没有必要安放 list_entry 调用在循环里了.

如果你查看 <linux/list.h> 里面, 你看到有一些附加的声明. hlist 类型是一个有一个单独的, 单指针列表头类型的双向链表; 它常用作创建哈希表和类型结构.

 
在Ubuntu上下载、编译、运行Android内核Linux Kernel 在Ubuntu上下载、编译、运行Android内核Linux Kernel在Ubuntu上下载、编译、运行Android系统一文中我们学习了如何下载、编译以及运行Android系统,一睹Android_4.4.2芳容。而唯一的美中不足是使用了预编译好的kernel-qemu-armv7。那么,在这篇文章中,我们就来弥补这个缺憾,编译运行属于自己的Android Kernel。同样,在这一篇文章中,咱 阅读详情

相关推荐

linux 怎么安装内核,如何在Linux中安装Linux kernel 5.4

Linus Torvalds在Linux Kernel邮件列表中宣布Linux Kernel 5.4已正式发布。Linux Kernel 5.4包括内核锁定模式,旨在加强UID 0与内核之间的边界; virtio-fs,一种高性能的virtio驱动程序,它允许虚拟客户机安装已在主机上导出的目录; fs-verity,用于检测文件篡改,例如dm-verity,但可用于文件而不是阻止设备; dm-cl...

weixin_42372573的博客 1223

Linux KernelLinux内核裁剪

Linux内核的版本号可以从源代码的顶层目录下的Makefile中看到,如图所示构成了linux的版本号:其中的"VERSION","PATCHLEVEL"组成主版本号,比如5.3、5.4等。稳定的版本的主版本号用偶数表示(比如:5.2、5.4),每隔2~3年出现一个稳定版本。开发中的版本号用奇数来表示(比如5.1、5.3),他是下一个稳定版本的前身。"SUBLEVL"称为次版本号,他不分奇偶,顺序递增。每隔1~2个月发布一个稳定版本。"EXTRAVERSION"称为扩展版本号,他不分奇偶,顺序递增。

qq_42723832的博客 8702

如何只安装linux内核,教你如何在Linux中安装Linux kernel 5.4

导读Linus Torvalds在Linux Kernel邮件列表中宣布Linux Kernel 5.4已正式发布。Linux Kernel 5.4包括内核锁定模式,旨在加强UID 0与内核之间的边界; virtio-fs,一种高性能的virtio驱动程序,它允许虚拟客户机安装已在主机上导出的目录; fs-verity,用于检测文件篡改,例如dm-verity,但可用于文件而不是阻止设备; dm-...

weixin_35214885的博客 1213

Linux内核Kernel启动过程

减少存储空间: 压缩内核映像可以显著减少其在存储设备上的占用空间。这对嵌入式系统、存储资源有限的设备以及需要快速分发和更新内核的环境尤其重要加快加载速度: 压缩文件占用的空间更小,这意味着启动加载程序从磁盘读取文件到内存中的时间会更短。虽然解压缩内核映像需要一些时间,但现代处理器的解压缩速度非常快,通常解压缩的时间比从存储设备读取更多数据的时间要少。这会整体上加快启动过程。提高传输效率: 在网络上传输内核映像时,压缩文件可以显著减少带宽使用量。这对于需要远程更新内核的系统(OTA)非常有利。

weixin_60669486的博客 3522

Linux内核4.14版本:ARM64内核启动过程(一)——start_kernel之前

目录 一、vmlinux.lds.S 二、head.S(进入kernel之前) 2.1 kernel之前bootloder的工作 2.1bootloader和kernel的交互的时候需求 2.3内核执行的第一行代码(_stext) 2.3.1preserve_boot_args() 2.3.2 el2_setup 2.3.3set_cpu_boot_mode_flag 2.3.4__create_page_tables 2.3.5__cpu_setup 2.3.6__pr...

yangguoyu8023的博客 6875

Centos7升级kernel-lt5.4内核

GRUB默认启动用户上一次选择的启动项。从这个git仓库下载长期内核版本。2025年4月1日,博主发现。更新 GRUB 配置。

教Linux的李老师 5496

Linux kernel 内核学习路线资料整理

以下整理自:http://bbs.csdn.net/topics/390759060 了解操作系统基本概念 如果不会,可以学习《操作系统:设计与实现》Andrew S.Tanenbaum 写的那本。以MINIX为例子讲解操作系统的概念。非常推荐。 了解Linux内核机制 有了操作系统的基本概念以后,可以了解Linux的机制了。推荐《Linux内核设计与实现》Robert Lo

im_Louis_LIU的博客 1万+

linux4.8内核详解,linux kernel 4.8以上内核NR_KERNEL_STACK的改变

1. NR_KERNEL_STACK是干什么用的?通过下面的命令,我们可以查看内核栈的数量:Shellcat /proc/vmstat | grep stacknr_kernel_stack 567123cat/proc/vmstat|grepstacknr_kernel_stack567这个值在内核中的宏为:NR_KERNEL_STACK,表示当前内核中有多少个内核栈。我们使用这个数字来监控K...

weixin_39899226的博客 503

linux kernellinux内核的init线程

linux内核的init线程 (注) 本文代码出自linux内核版本:4.1.15(小生的文章若无特别说明,都将基于此版本) 本文代码的出处文件以(/)开头,代表linux内核的安装目录。(小生的文章若无特别说明,都有这样约定) 文章目录linux内核的init线程一、概述二、init线程的入口函数kernel_init()三、kernel_init_freeable()分析四、kernel_init()余下部分的分析(4-1)一些基础的操作(4-2)启动ramdisk_execute_co

iriczhao的博客 2838

linux kernel 0.11学习

linux kernel 0.11学习

青少年趣味编程 1654

linux kernellinux内核入口:head.o

一、开篇 对于linux内核来说,她有一个统一的入口,那就是head.o模块,在不同架构下,该模块有着不同的文件名称,例如head.S、head_xxx.S;该文件的具体路径是(/arch//kernel/head.S),其中是具体的架构名称。head.o模块用于完成和架构、CPU相关的初始化工作,主要用于为内核主体的执行做准备。除此之外,head.o的功能还有: 检查处理器和架构的有效性。 创建初始的页表表项。 启用处理器的内存管理单元(MMU)。 进行错误检测并报告。 跳转到内核主体的起始位置。(ma

iriczhao的博客 5309

Linux kernel下的内核socket编程

内核4版本下测试通过,引用了高手的代码,做了修改,避免崩溃:https://www.linuxquestions.org/questions/blog/915086731-521769/implementation-of-server-client-socket-under-kernel-environment-37716/server:#include&lt;linux/in.h&gt; #...

4370

[linux.kernel,c]如何调用内核中的函数

在自己编写的程序中引用内核中的库函数,引用内核函数方法步骤介绍

inuyasha1027的专栏 8745

linux kernellinux内核中的debugfs

本文分析和讨论linux内核中debugfs常用的API和使用方法。

iriczhao的博客 978

Linux kernel 分析之五:内核启动-内核解压缩

这得从vmliux.bin的产生过程说起。 从内核的生成过程来看内核的链接主要有三步: 第一步是把内核的源代码编译成.o文件,然后链接,这一步,链接的是arch/i386/kernel/head.S,生成的是vmlinux。注意的是这里的所有变量地址都是32位页寻址方式的保护模式下的虚拟地址。通常在3G以上。 /* * linux/arch/i386/kernel/head.S --

vanileo的专栏 4139

linux kernel 学习手记2 Alloc memory -kmalloc

      第八章 分配内存1.Kmalloc函数的内幕Kmalloc内存分配和malloc相似,除非被阻塞否则他执行的速度非常快,而且不对获得空间清零。Flags参数#includeVoid *kmalloc(size_t size, int flags);第一个参数是要分配的块的大小,第二个参数是分配标志(flags),他提供了多种kmalloc的行为。最常用的GF

1987

如何在Linux内核中运行WebAssembly?kernel-wasm完整入门指南

想要在Linux内核中安全高效地运行WebAssembly代码吗?kernel-wasm项目为你提供了终极解决方案!这个创新的内核模块让你能够在Linux内核空间中直接运行WebAssembly字节码,实现比原生代码更快的执行速度。本文将为你提供完整的kernel-wasm入门指南,从安装配置到实际运行,一步步带你掌握这个强大的内核级WebAssembly运行时。 ## 📦 kernel-wa

gitblog_01191的博客 658

内核源码中版本号详解(KERNEL_VERSION & KERNEL_VERSION)

内核源码的版本号详解

weixin_42031299的博客 6553
上一篇: linux kernel 学习手记3 Interrupt
下一篇: Linux 调度器在2.6.23中的修改
human3000
博客等级 码龄26年 22粉丝 18原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值