覃谈谭的blog


动态内存分配

0 条评论 学习总结 c adtxl

目录:

1.malloc和free

函数原型如下,在头文件stdlib.h中声明:

void *malloc(size_t size);
void free(void *pointer);

malloc的参数是需要分配的内存字节数。如果内存池中的可用内存可以满足这个需求,malloc就返回一个指向被分配的内存块起始位置的指针。malloc分配的是一块连续的内存,并且不对这块内存进行任何的初始化。
如果操作系统无法向malloc提供更多的内存,malloc就返回一个NULL指针。因此,对每个从malloc返回的指针都进行检查,确保它并非NULL是非常重要的。
free的参数必须要么是NULL,要么是一个先前从malloc、calloc或realloc返回的值。向free传递一个NULL参数不会产生任何效果。
malloc返回一个类型为void*的指针,一个void*类型的指针可以转换成其它任何类型的指针

2.calloc和realloc

calloc和realloc的函数原型如下所示:

void *calloc(size_t num_elements, size_t element_size);
void realloc(void *ptr, size_t new_size);

malloc和calloc之间的主要区别是calloc在返回指向内存的指针之前把它初始化为0。
calloc和malloc之间另一个较小的区别是它们请求内存数量的方式不同。calloc的参数包括所需元素的数量和每个元素的字节数。根据这些值,它能够计算出总共需要分配的内存。
realloc函数用于修改一个原先已经分配的内存块的大小。使用这个函数,可以使一块内存扩大或缩小。如果它用于扩大一个内存块,那么这块内存原先的内容依然保留,新增加的内存添加到原先内存块的后面,新内存并未以任何方法进行初始化。如果它用于缩小内存块,该内存块尾部的部分内存便被拿掉,剩余部分内存的原先内容依然保留。
如果原先的内存块无法改变大小,realloc将分配另一块正确大小的内存,并把原先那块内存的内容复制到新块上。因此,在使用realloc之后,你就不能再使用指向旧内存的指针,而是应该用realloc所返回的新指针。

3. 常见的动态内存错误

在使用动态内存分配的程序中,常常会出现许多错误。这些错误包括对NULL指针进行解引用操作、对分配的内存进行操作时越过边界、释放并非动态分配的内存、试图释放一块动态分配的内存以及一块动态内存被释放之后被继续使用。

  • 动态内存分配最常见的错误就是忘记检查所请求的内存是否成功分配
    // 记得对分配的内存进行检查
    if (new_mem == NULL)
    printf( "Out of memory" );
    exit(1);
  • 动态内存分配的第二大错误来源是操作内存时超出了分配内存的边界
    在malloc和free的有些实现中,它们以链表的形式维护可用的内存池。对分配的内存之外的区域进行访问可能破坏这个链表,这有可能产生异常,从而终止程序。

gcc和objdump

0 条评论 学习总结 Linux c gcc adtxl

1. gcc

  • -ansi
    关闭 gnu c中与ansi c不兼容的特性,激活ansi c的专有特性 ( 包括禁止一些 asm inline typeof 关键字 , 以及 UNIX,vax 等预处理宏

  • -lxx
    表示动态加载libxx.so库

  • -Lxx
    表示增加目录xx,让编译器可以在xx下寻找库文件

  • -Ixx
    表示增加目录xx,让编译器可以在xx下寻找头文件优化选项

  • -shared
    生成共享目标文件。通常用在建立共享库时

  • -Wall
    生成所有警告信息。一下是具体的选项,可以单独使用

简单的GCC语法:

如果你只有一个文件(或者只有几个文件),那么就可以不写Makefile文件(当然有Makefile更加方便),用gcc直接编译就行了。在这里我们只介绍几个我经常用的几个参数,第一是 “-o”,它后面的参数表示要输出的目标文件,再一个是 “-c”,表示仅编译(Compile),不链接(Make),如果没有”-c”参数,那么就表示链接,如下面的几个命令:

gcc –c test.c,表示只编译test.c文件,成功时输出目标文件test.o

gcc –c test.c –o test.o ,与上一条命令完全相同

gcc –o test test.o,将test.o连接成可执行的二进制文件test

gcc –o test test.c,将test.c编译并连接成可执行的二进制文件test

gcc test.c –o test,与上一条命令相同

gcc –c test1.c,只编译test1.c,成功时输出目标文件test1.o

gcc –c test2.c,只编译test2.c,成功时输出目标文件test2.o

gcc –o test test1.o test2.o,将test1.o和test2.o连接为可执行的二进制文件test

gcc –c test test1.c test2.c,将test1.o和test2.o编译并连接为可执行的二进制文件test

注:如果你想编译cpp文件,那么请用g++,否则会有类似如下莫名其妙的错误:

cc3r3i2U.o(.eh_frame+0x12): undefined reference to `__gxx_personality_v0’......

还有一个参数是”-l”参数,与之紧紧相连的是表示连接时所要的链接库,比如多线程,如果你使用了pthread_create函数,那么你就应该在编译语句的最后加上”-lpthread”,”-l”表示连接,”pthread”表示要连接的库,注意他们在这里要连在一起写,还有比如你使用了光标库curses,那么呢就应该在后面加上”-lcurses”,比如下面的写法:

gcc –o test test1.o test2.o –lpthread –lcurses

例如: 在ubuntu 环境下编译基于course库函数的程序时,如果不带 -lncurses时,会出现

screen1.c:(.text+0x12):对‘initscr’未定义的引用
screen1.c:(.text+0x24):对‘wmove’未定义的引用
screen1.c:(.text+0x39):对‘printw’未定义的引用
screen1.c:(.text+0x4a):对‘wrefresh’未定义的引用
screen1.c:(.text+0x5f):对‘endwin’未定义的引用
需使用 gcc -o screen1 screen1.c -lncurses

2. objdump

gcc命令之 objdump
---------------objdump是用查看目标文件或者可执行的目标文件的构成的GCC工具----------
以下3条命令足够那些喜欢探索目标文件与源代码之间的丝丝的关系的朋友。
objdump -x obj 以某种分类信息的形式把目标文件的数据组织(被分为几大块)输出 <可查到该文件的所有动态库>
objdump -t obj 输出目标文件的符号表()
objdump -h obj 输出目标文件的所有段概括()
objdump -j .text/.data -S obj 输出指定段的信息,大概就是反汇编源代码把
objdump -S obj C语言与汇编语言同时显示
以下为网上摘录文章。

关于nm -s的显示请自己man nm查看
objdump命令的man手册
objdump - 显示二进制文件信息
objdump
[-a] [-b bfdname |
--target=bfdname] [-C] [--debugging]
[-d] [-D]
[--disassemble-zeroes]
[-EB|-EL|--endian={big|little}] [-f]
[-h] [-i|--info]
[-j section | --section=section]
[-l] [-m machine ] [--prefix-addresses]
[-r] [-R]
[-s|--full-contents] [-S|--source]
[--[no-]show-raw-insn] [--stabs] [-t]
[-T] [-x]
[--start-address=address] [--stop-address=address]
[--adjust-vma=offset] [--version] [--help]
objfile...

--archive-headers
-a 显示档案库的成员信息,与 ar tv 类似
objdump -a libpcap.a
和 ar -tv libpcap.a 显示结果比较比较
显然这个选项没有什么意思。
--adjust-vma=offset
When dumping information, first add offset to all
the section addresses. This is useful if the sec-
tion addresses do not correspond to the symbol
table, which can happen when putting sections at
particular addresses when using a format which can
not represent section addresses, such as a.out.
-b bfdname
--target=bfdname
指定目标码格式。这不是必须的,objdump能自动识别许多格式,
比如:objdump -b oasys -m vax -h fu.o
显示fu.o的头部摘要信息,明确指出该文件是Vax系统下用Oasys
编译器生成的目标文件。objdump -i将给出这里可以指定的
目标码格式列表
--demangle
-C 将底层的符号名解码成用户级名字,除了去掉所有开头
的下划线之外,还使得C++函数名以可理解的方式显示出来。
--debugging
显示调试信息。企图解析保存在文件中的调试信息并以C语言
的语法显示出来。仅仅支持某些类型的调试信息。
--disassemble
-d 反汇编那些应该还有指令机器码的section
--disassemble-all
-D 与 -d 类似,但反汇编所有section
--prefix-addresses
反汇编的时候,显示每一行的完整地址。这是一种比较老的反汇编格式。
显示效果并不理想,但可能会用到其中的某些显示,自己可以对比。
--disassemble-zeroes
一般反汇编输出将省略大块的零,该选项使得这些零块也被反汇编。
-EB
-EL
--endian={big|little}
这个选项将影响反汇编出来的指令。
little-endian就是我们当年在dos下玩汇编的时候常说的高位在高地址,
x86都是这种。

--file-headers
-f 显示objfile中每个文件的整体头部摘要信息。

--section-headers
--headers
-h 显示目标文件各个section的头部摘要信息。

--help 简短的帮助信息。

--info
-i 显示对于 -b 或者 -m 选项可用的架构和目标格式列表。

--section=name
-j name 仅仅显示指定section的信息

--line-numbers
-l 用文件名和行号标注相应的目标代码,仅仅和-d、-D或者-r一起使用
使用-ld和使用-d的区别不是很大,在源码级调试的时候有用,要求
编译时使用了-g之类的调试编译选项。

--architecture=machine
-m machine
指定反汇编目标文件时使用的架构,当待反汇编文件本身没有描述
架构信息的时候(比如S-records),这个选项很有用。可以用-i选项
列出这里能够指定的架构

--reloc
-r 显示文件的重定位入口。如果和-d或者-D一起使用,重定位部分以反汇
编后的格式显示出来。

--dynamic-reloc
-R 显示文件的动态重定位入口,仅仅对于动态目标文件有意义,比如某些
共享库。

--full-contents
-s 显示指定section的完整内容。

objdump --section=.text -s inet.o | more

--source
-S 尽可能反汇编出源代码,尤其当编译的时候指定了-g这种调试参数时,
效果比较明显。隐含了-d参数。

--show-raw-insn
反汇编的时候,显示每条汇编指令对应的机器码,除非指定了
--prefix-addresses,这将是缺省选项。

--no-show-raw-insn
反汇编时,不显示汇编指令的机器码,这是指定 --prefix-addresses
选项时的缺省设置。

--stabs
Display the contents of the .stab, .stab.index, and
.stab.excl sections from an ELF file. This is only
useful on systems (such as Solaris 2.0) in which
.stab debugging symbol-table entries are carried in
an ELF section. In most other file formats, debug-
ging symbol-table entries are interleaved with
linkage symbols, and are visible in the --syms output.

--start-address=address
从指定地址开始显示数据,该选项影响-d、-r和-s选项的输出。

--stop-address=address
显示数据直到指定地址为止,该选项影响-d、-r和-s选项的输出。

--syms
-t 显示文件的符号表入口。类似于nm -s提供的信息

--dynamic-syms
-T 显示文件的动态符号表入口,仅仅对动态目标文件有意义,比如某些
共享库。它显示的信息类似于 nm -D|--dynamic 显示的信息。

--version 版本信息

objdump --version

--all-headers
-x 显示所有可用的头信息,包括符号表、重定位入口。-x 等价于
-a -f -h -r -t 同时指定。

objdump -x inet.o

参看 nm(1)

★ objdump应用举例(待增加)

/
g++ -g -Wstrict-prototypes -Wall -Wunused -o objtest objtest.c
/

include

include

int main ( int argc, char * argv[] )
{
execl( "/bin/sh", "/bin/sh", "-i", 0 );
return 0;
}

g++ -g -Wstrict-prototypes -Wall -Wunused -o objtest objtest.c
objdump -j .text -Sl objtest | more
/main(查找)

08048750:
main():
/home/scz/src/objtest.c:7
*/

include

include

int main ( int argc, char * argv[] )
{
8048750: 55 pushl %ebp
8048751: 89 e5 movl %esp,%ebp
/home/scz/src/objtest.c:8
execl( "/bin/sh", "/bin/sh", "-i", 0 );
8048753: 6a 00 pushl $0x0
8048755: 68 d0 87 04 08 pushl $0x80487d0
804875a: 68 d3 87 04 08 pushl $0x80487d3
804875f: 68 d3 87 04 08 pushl $0x80487d3
8048764: e8 db fe ff ff call 8048644 <_init+0x40>
8048769: 83 c4 10 addl $0x10,%esp
/home/scz/src/objtest.c:9
return 0;
804876c: 31 c0 xorl %eax,%eax
804876e: eb 04 jmp 8048774
8048770: 31 c0 xorl %eax,%eax
8048772: eb 00 jmp 8048774
/home/scz/src/objtest.c:10
}
8048774: c9 leave
8048775: c3 ret
8048776: 90 nop

如果说上面还不够清楚,可以用下面的命令辅助一下:

objdump -j .text -Sl objtest --prefix-addresses | more
objdump -j .text -Dl objtest | more

用以上不同的命令去试会得到惊喜!


目标文件格式

0 条评论 学习总结 c ELF adtxl

编译器编译源代码后生成的文件叫做目标文件。目标文件从结构上讲,它是已经编译后的可执行文件格式,只是还没有经过链接的过程,其中可能有些符号或有些地址还没有被调整。其实它本身就是按照可执行文件格式存储的,只是跟真正的可执行文件在结构上稍有不同。

1.1 目标文件的格式

Linux下的可执行文件格式是ELF(Executable Linkable Format)。
ELF文件标准把系统中采用的ELF格式的文件分为四类,分别是:

  • 可重定位文件(Relocatable File)
    如Linux下的.o
  • 可执行文件(Executable File)
  • 共享目标文件(Shared Object File)
    如Linux下的.so
  • 核心转储文件(Core Dump File)
    当进程意外终止时,系统可以将该进程的地址空间的内容及终止时的一些其它信息转储到核心转储文件
    如Linux下的core dump
    Linux下使用file命令可以查看相应的文件格式,
    file test.o

1.2 目标文件时什么样的

  • section(节)与segment(段)
    二者唯一的区别是在ELF的链接视图和装载视图的时候
  • code section(代码段)
    存放编译后的机器指令,如".code"、".text"
  • data section(数据段)
    存放编译后的全局变量和局部静态变量,如.data
  • 文件头
    ELF文件的开头是一个“文件头”,它描述了整个文件的文件属性,包括文件是否可执行、是静态链接还是动态链接及入口地址(如果是可执行文件)、目标硬件、目标操作系统等信息,文件头还包括一个段表。
  • section table(段表)
    段表是一个描述文件中各个段的数组。段表描述了文件中各个段在文件中的偏移位置及段的属性等,从段表里面可以得到各个段的所有信息。
  • .bss段
    未初始化的全局变量和局部静态变量一般放在.bss段。程序运行的时候这些变量确是要占内存空间的,并且可执行文件必须记录所有未初始化的全局变量和局部静态变量的大小总和。
    所以.bss段只是为位初始化的全局变量和局部静态变量预留位置而已,它并没有内容,所以在文件中也不占据空间。
    总体来说,程序源代码被编译以后主要分成两种段:程序指令和程序数据。代码段属于程序指令,而数据段和.bss段属于程序数据。

1.3 一个实例:simple_section.o

simple_section.c代码如下:

/*
* Linux: gcc -c simple-section.c
 */

int printf( const char* format, ... );


int global_int_var = 84;
int global_uninit_var;

void func1(int i)
{
        printf( "%d\n", i);
}

int main(void)
{
        static int static_var = 85;
        static int static_var2;

        int a = 1;
        int b;

        func1( static_var + static_var2 + a + b );

        return a;
}

使用gcc编译文件(-c参数表示只编译不链接)

gcc -c simple_section.c 

使用objdump查看目标文件的结构和内容,Linux下还有一个工具readelf,是专门针对elf文件格式的解析器
参数-h把各个段的信息打印出来,-x打印更多的信息

objdump -h simple_section.o

显示如下:

user@user-HP-ProDesk-600-G5-MT:~/txl/code/c/simlesection$ objdump -h simple_section.o

simple_section.o:     文件格式 elf64-x86-64

节:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000057  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000008  0000000000000000  0000000000000000  00000098  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000004  0000000000000000  0000000000000000  000000a0  2**2
                  ALLOC
  3 .rodata       00000004  0000000000000000  0000000000000000  000000a0  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .comment      0000002a  0000000000000000  0000000000000000  000000a4  2**0
                  CONTENTS, READONLY
  5 .note.GNU-stack 00000000  0000000000000000  0000000000000000  000000ce  2**0
                  CONTENTS, READONLY
  6 .eh_frame     00000058  0000000000000000  0000000000000000  000000d0  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA

其中Size表示段的长度,File off表示段所在的位置,每个段的第二行CONTENTS, ALLOC等表示段的属性,“CONTENTS”表示段在文件中存在。
使用size命令,可以用来查看ELF文件代码段、数据段和bss段的长度(dec表示3个段长度和的十进制,hex表示长度和的十六进制)

user@user-HP-ProDesk-600-G5-MT:~/txl/code/c/simlesection$ size simple_section.o
   text    data     bss     dec     hex filename
    179       8       4     191      bf simple_section.o

objdump的“-s”参数可以将所有段的内容以十六进制的方式打印出来,“-d”参数可以将所有包含指令的段反汇编

objdump -s -d simple_section.o

显示内容如下:

simple_section.o:     文件格式 elf64-x86-64

Contents of section .text:
 0000 554889e5 4883ec10 897dfc8b 45fc89c6  UH..H....}..E...
 0010 488d3d00 000000b8 00000000 e8000000  H.=.............
 0020 0090c9c3 554889e5 4883ec10 c745f801  ....UH..H....E..
 0030 0000008b 15000000 008b0500 00000001  ................
 0040 c28b45f8 01c28b45 fc01d089 c7e80000  ..E....E........
 0050 00008b45 f8c9c3                      ...E...
Contents of section .data:
 0000 54000000 55000000                    T...U...
Contents of section .rodata:
 0000 25640a00                             %d..
Contents of section .comment:
 0000 00474343 3a202855 62756e74 7520372e  .GCC: (Ubuntu 7.
 0010 352e302d 33756275 6e747531 7e31382e  5.0-3ubuntu1~18.
 0020 30342920 372e352e 3000               04) 7.5.0.
Contents of section .eh_frame:
 0000 14000000 00000000 017a5200 01781001  .........zR..x..
 0010 1b0c0708 90010000 1c000000 1c000000  ................
 0020 00000000 24000000 00410e10 8602430d  ....$....A....C.
 0030 065f0c07 08000000 1c000000 3c000000  ._..........<...
 0040 00000000 33000000 00410e10 8602430d  ....3....A....C.
 0050 066e0c07 08000000                    .n......

Disassembly of section .text:

0000000000000000 <func1>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 83 ec 10             sub    $0x10,%rsp
   8:   89 7d fc                mov    %edi,-0x4(%rbp)
   b:   8b 45 fc                mov    -0x4(%rbp),%eax
   e:   89 c6                   mov    %eax,%esi
  10:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi        # 17 <func1+0x17>
  17:   b8 00 00 00 00          mov    $0x0,%eax
  1c:   e8 00 00 00 00          callq  21 <func1+0x21>
  21:   90                      nop
  22:   c9                      leaveq
  23:   c3                      retq

0000000000000024 <main>:
  24:   55                      push   %rbp
  25:   48 89 e5                mov    %rsp,%rbp
  28:   48 83 ec 10             sub    $0x10,%rsp
  2c:   c7 45 f8 01 00 00 00    movl   $0x1,-0x8(%rbp)
  33:   8b 15 00 00 00 00       mov    0x0(%rip),%edx        # 39 <main+0x15>
  39:   8b 05 00 00 00 00       mov    0x0(%rip),%eax        # 3f <main+0x1b>
  3f:   01 c2                   add    %eax,%edx
  41:   8b 45 f8                mov    -0x8(%rbp),%eax
  44:   01 c2                   add    %eax,%edx
  46:   8b 45 fc                mov    -0x4(%rbp),%eax
  49:   01 d0                   add    %edx,%eax
  4b:   89 c7                   mov    %eax,%edi
  4d:   e8 00 00 00 00          callq  52 <main+0x2e>
  52:   8b 45 f8                mov    -0x8(%rbp),%eax
  55:   c9                      leaveq
  56:   c3                      retq

在.text段,最左面一列是偏移量,中间四列是十六进制内容,最右面一列是.text段的ASCII码形式。
.data段保存的是那些已经初始化了的全局静态变量和局部静态变量。
字符串常量"%d\n",它是一种只读数据,所以它被放到了“.rodata”段,我们可以从输出结果看到".rodata"这个段的4个字节刚好是这个字符串常量的ASCII字节序,最后以\0结尾。
.bss段存放的是未初始化的全局变量和局部静态变量。
还有一些其它的常用段如下:

常用的段名 说明
.rodata 跟.rodata一样
.interp /lib64/ld-linux.so.2(动态链接器的路径,有入口函数,装载的时候启动)
.dynamic 动态链接信息
.symtab 用于静态链接和调试(符号表保留在文件中,不加载进内存)
.dynsym 用于动态链接(符号表会被加载进内存)
.init和.finit 1 共享对象可能会持有这两个段,做为共享对象的入口和出口函数 2 c++中的全局对象,或者static对象的构造函数和析构函数
.comment 存放的是编译器版本信息
.debug 调试信息
.plt和.got 动态链接的跳转表和全局入口表

1.4 ELF文件结构描述

ELF目标文件格式的最前部是ELF文件头,它描述了整个文件的基本属性,比如ELF文件版本、目标机器型号、程序入口地址等。紧接着就是各个段。其中ELF文件中与段有关的重要结构就是段表,该表描述了ELF文件包含的所有段的信息,比如每个段的段名、段的长度、在文件中的偏移、读写权限及段的其他属性。

1.4.1 文件头

使用readelf命令来详细查看ELF文件,

readelf -h simple_section.o

输出如下:

ELF 头:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  类别:                              ELF64
  数据:                              2 补码,小端序 (little endian)
  版本:                              1 (current)
  OS/ABI:                            UNIX - System V
  ABI 版本:                          0
  类型:                              REL (可重定位文件)
  系统架构:                          Advanced Micro Devices X86-64
  版本:                              0x1
  入口点地址:               0x0
  程序头起点:          0 (bytes into file)
  Start of section headers:          1104 (bytes into file)
  标志:             0x0
  本头的大小:       64 (字节)
  程序头大小:       0 (字节)
  Number of program headers:         0
  节头大小:         64 (字节)
  节头数量:         13
  字符串表索引节头: 12

ELF魔数:最前面的16个字节刚好对应“Elf32_Ehdr”的e_ident这个成员。

1.4.2 段表

使用readelf工具来查看文件的段,它显示出来的结果才是真正的段表结构

readelf -S simple_section.o

输出如下:

There are 13 section headers, starting at offset 0x450:

节头:
  [号] 名称              类型             地址              偏移量
       大小              全体大小          旗标   链接   信息   对齐
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000000000  00000040
       0000000000000057  0000000000000000  AX       0     0     1
  [ 2] .rela.text        RELA             0000000000000000  00000340
       0000000000000078  0000000000000018   I      10     1     8
  [ 3] .data             PROGBITS         0000000000000000  00000098
       0000000000000008  0000000000000000  WA       0     0     4
  [ 4] .bss              NOBITS           0000000000000000  000000a0
       0000000000000004  0000000000000000  WA       0     0     4
  [ 5] .rodata           PROGBITS         0000000000000000  000000a0
       0000000000000004  0000000000000000   A       0     0     1
  [ 6] .comment          PROGBITS         0000000000000000  000000a4
       000000000000002a  0000000000000001  MS       0     0     1
  [ 7] .note.GNU-stack   PROGBITS         0000000000000000  000000ce
       0000000000000000  0000000000000000           0     0     1
  [ 8] .eh_frame         PROGBITS         0000000000000000  000000d0
       0000000000000058  0000000000000000   A       0     0     8
  [ 9] .rela.eh_frame    RELA             0000000000000000  000003b8
       0000000000000030  0000000000000018   I      10     8     8
  [10] .symtab           SYMTAB           0000000000000000  00000128
       0000000000000198  0000000000000018          11    11     8
  [11] .strtab           STRTAB           0000000000000000  000002c0
       000000000000007c  0000000000000000           0     0     1
  [12] .shstrtab         STRTAB           0000000000000000  000003e8
       0000000000000061  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

1.4.3 重定位表

.rela.text类型位RELA,是一个重定位表。链接器在处理目标文件时,需要对目标文件中某些部位进行重定位,即代码段和数据段中那些绝对地址的引用的位置。这些重定位信息都记录在重定位表里面。.rela.text段就是.text段的重定位表,因为.text段中至少有一个绝对地址的引用,那就是对printf函数的调用。而.data段则没有对绝对地址的引用,它只包含了几个常量,所以没有针对data段的重定位表。

1.4.4 字符串表

.strtab(string table): 字符串表,用来保存普通的字符串
.shstrtab(section header string table):用来保存段表中用到的字符串,最常见的就是段名


内核配置

0 条评论 学习总结 Linux 内核 adtxl

1.内核源码编译过程

  • 1.遍历每个源码目录的(或配置指定的源码目录)Makefile
  • 2.每个目录的Makefile会根据Kconfig文件定制要编译的对象
  • 3.最后回到顶层目录的Makefile执行编译
    据此,我们可以得出各个文件的作用
  • Kconfig--->(每个源码目录下)提供选项
  • .config--->(源码顶层目录下)保存选择结果
  • Makefile--->(每个源码目录下)根据.config中的内容来告知编译系统如何编译

2. Kconfig基本语法

2.1 单一选项

总体原则:每一个config就是一个选项,最上面跟着控制句柄,下面则是对这个选项的配置,如选项名是什么,依赖什么,选中这个后同时会选择什么。

config CPU_S5PC100
    bool "选项名"
    select S5P_EXT_INT
    select SAMSUNG_DMADEV
    help
      Enable S5PC100 CPU support

config —> 选项
CPU_S5PC100 —>句柄,可用于控制Makefile 选择编译方式
bool —>选择可能:TRUE选中、FALSE不选 选中则编译,不选中则不编译。
如果后面没有字符串名称,则表示其不会出现在选择软件列表中
select —> 当前选项选中后则select后指定的选项自动被选择

depends on ARM || BLACKFIN || MIPS || COLDFIRE

depend on 依赖,后面的四个选择其中至少一个被选择,这个选项才能被选

config DM9000
    tristate "DM9000 support"

tristate —> 选中并编译进内核、不选编译成模块

2.2 选项为数字

config ARM_DMA_IOMMU_ALIGNMENT
    int "Maximum PAGE_SIZE order of alignment for DMA IOMMU buffers" ---->该选项是一个整型值
    range 4 9 ---->该选项的范围值
    default 8 ---->该选项的默认值
    help
      DMA mapping framework by default aligns all buffers to the smallest
      ...

这里的defult其实也可以用在bool中

config STACKTRACE_SUPPORT
    bool    --->该选项可以选中或不选,且不会出现在选择列表中
    default y ---->表示缺省情况是选中

2.3 if..endif

if ARCH_S5PC100 --->如果ARCH_S5PC100选项选中了,则在endif范围内的选项才会被选

config CPU_S5PC100
    bool "选项名"
    select S5P_EXT_INT
    select SAMSUNG_DMADEV
    help
      Enable S5PC100 CPU support

endif

举个例子,如果CPU没有选择使用多核CPU,则不会出现CPU个数的选项。

2.4 choice多个选项

choice      --->表示选择列表
    prompt "Default I/O scheduler"         //主目录名字
    default DEFAULT_CFQ                    //默认CFQ
    help
      Select the I/O scheduler which will be used by default for all
      block devices.

    config DEFAULT_DEADLINE
        bool "Deadline" if IOSCHED_DEADLINE=y 

    config DEFAULT_CFQ
        bool "CFQ" if IOSCHED_CFQ=y

    config DEFAULT_NOOP
        bool "No-op"

endchoice

2.5 menu与menuconfig

menu "Boot options"  ----> menu表示该选项是不可选的菜单,其后是在选择列表的菜单名

config USE_OF
    bool "Flattened Device Tree support"
    select IRQ_DOMAIN
    select OF
    select OF_EARLY_FLATTREE
    help
      Include support for flattened device tree machine descriptions.
....

endmenu     ----> menu菜单结束

menu指的是不可编辑的menu,而menuconfig则是带选项的menu
menu和choice的区别
menu 可以多选 choice 是单项选择题

menuconfig的用法

menuconfig MODULES ---> menuconfig表示MODULE是一个可选菜单,其选中后是CONFIG_MODULES
    bool "菜单名"
if MODULES
...
endif # MODULES

说到底,menconfig 就是一个带选项的菜单,在下面需要用bool判断一下,选择成立后,进入if …endif 中间得空间。

3.基本使用方法

将写好的驱动添加到内核编译选项中

  • 1.配置Kconfig

在driver目录下新建一个目录test,进入test目录,创建Kconfig文件

config TEST
        bool "Test driver"
        help
        this for test!

这里定义了一个TEST的句柄,Kconfig可以通过这个句柄来控制Makefile中是否编译,”Test driver”是显示在终端的名称。

  • 2.配置Makefile

在test目录,新建一个Makefile

obj-$(CONFIG_TEST) += test.o

意义:

obj-$(CONFIG_选项名) += xxx.o
/* 当CONFIG_选项名=y时,表示对应目录下的xxx.c将被编译进内核
当CONFIG_选项名=m时对应目录下的xxx.c将被编译成模块*/
  • 3.配置上层目录的Makefile与Kconfig

在上一层目录的Kconfig中,

menu "Device Drivers"
source "drivers/test/Kconfig"

表示将test文件夹中的Kconfig加入搜寻目录
在上一层目录的Makefile中,更改如下

obj-y   += irqchip/
obj-y   += bus/
obj-y   += test/        #新添加

最后,运行make menuconfig,可进行配置,保存后生成.config文件。


字符设备驱动

0 条评论 学习总结 Linux驱动 adtxl

1.Linux字符设备驱动结构

1.1 cdev结构体

在Linux内核中,使用cdev结构体描述一个字符设备,cdev结构体定义如下,

struct cdev {
    struct kobject kobj;    /* 内嵌的kobject对象 */
    struct module *owner;   /* 所属模块 */
    struct file_operations *ops;    /* 文件操作结构体 */
    struct list_head list;
    dev_t dev;      /* 设备号 */
    unsigned int count;
}

cdev结构体的dev_t成员定义了设备号,为32位,其中12位为主设备号,20位为次设备号。使用下列宏可以从dev_t获得主设备号和次设备号;

MAJOR(dev_t dev)
MINOR(dev_t dev)

使用下列宏可以通过主设备号和次设备号生成dev_t:

MKDEV(int major, int minor)

Linux内核提供了一组函数以用于操作cdev结构体:

/* 初始化cdev成员,并建立cdev和file_operations之间的连接*/
void cdev_init(struct cdev *, struct file_operations *);
/* 动态申请一个cdev内存 */
struct cdev *cdev_alloc(void);
/*  */
void cdev_put(struct cdev *p);
/* 向系统添加一个cdev,完成字符设备的注册。对cdev_add()的调用通常发生在字符设备驱动模块加载函数中 */
int cdev_add(struct cdev *, dev_t, unsigned);
/* 对cdev_dev()函数的调用通常发生在字符设备驱动模块卸载函数中 */
void cdev_del(struct cdev *);

1.2 分配和释放设备号

在调用cdev_add()函数向系统注册字符设备之前,应首先调用register_chrdev_region()或alloc_chrdev_region()函数向系统申请设备号,相应地,在调用cdev_del()函数从系统注销字符设备之后,unregister_chrdev_region()会被调用以释放原先申请的设备号,函数原型为:

/* 已知起始设备的设备号的情况,from:要分配设备编号范围的初始值(次设备号常设为0),count为连续编号范围,name为编号相关联的设备名称 */
int register_chrdev_region(dev-t from, unsigned count, const char *name);
/* 设备号未知,向系统动态申请未被占用的设备号的情况,函数调用成功后,会把得到的设备号放入第一个参数dev中,其优点是自动避开设备号重复的冲突 */
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
/* 在调用cdev_del()函数从系统注销字符设备之后,unregister_chrdev_region()被调用以释放原先申请的设备号 */
void unregister_chrdev_region(dev_t from, unsigned count);

1.3 file_operations结构体

file_operations结构体中的成员函数是字符设备驱动程序设计的主体内容,这些函数实际会在应用程序进行Linux的open()、write()、read()、close()等系统调用时最终被内核调用。file_operations结构体的主要成员:

  • llseek()函数用来修改一个文件的当前读写位置,并将新位置返回,在出错时,这个函数返回一个负值。
  • read()函数用来从设备中读取数据,成功时函数返回读取的字节数,出错时返回一个负值。
  • write()函数向设备发送数据,成功时该函数返回写入的字节数。
  • read()和write()如果返回0,则暗示end-of-file(EOF)
  • unlocked_ioctl()提供设备相关控制命令的实现(既不是读操作,也不是写操作),当调用成功时,返回给调用程序一个非负值。
  • mmap()函数将设备内存映射到进程的虚拟地址空间中,如果设备驱动未实现此函数,用户进行mmap()系统调用时将获得-ENODEV返回值。
  • 当用户空间调用Linux API函数open()打开设备文件时,设备驱动的open()函数最终被调用。驱动程序可以不实现这个函数,在这种情况下,设备的打开操作永远成功。与open()对应的是release()函数。
  • poll()函数一般用于询问设备是否可被非阻塞地立即读写。当询问的条件未触发时,用户空间进行select()和poll()系统调用将引起进程的阻塞。
  • aio_read()和aio_write()函数分别对与文件描述符对应的设备进行异步读、写操作。

1.4 字符设备驱动的组成

1.4.1 字符设备驱动模块加载与卸载函数

在字符设备驱动模块加载函数中应该实现设备号的申请和cdev的注册,而在卸载函数中应实现设备号的释放和cdev的注销。
Linux内核的编码习惯是为设备定义一个设备相关的结构体,该结构体包含设备所涉及的cdev、私有数据及锁等信息。

1.4.2 字符设备驱动的file_operations结构体中成员函数

file_operations结构体中的成员函数是字符设备驱动与内核虚拟文件系统的结口,是用户空间对Linux进行系统调用最终的落实者。大多数字符设备会实现read()、write()和ioctl()函数。
由于用户空间不能直接访问内核空间的内存,因此借助了函数copy_from_user()完成用户空间缓冲区到内核空间的复制,以及copy_to_user()完成内核空间到用户空间缓冲区的复制。
以上函数均返回不能被复制的字节数,因此,如果完全复制成功,返回值为0.如果复制失败,则返回负值。
如果复制的内存是简单类型,如char、int、long等,则可以使用简单的put_user()和get_user()


IDR机制分析

0 条评论 学习总结 Linux IDR adtxl

1.IDR简述

idr在linux内核中指的就是整数ID管理机制,从本质上来说,这就是一种将整数ID号和特定指针关联在一起的机制。这个机制最早是在2003年2月加入内核的,当时是作为POSIX定时器的一个补丁。现在,在内核的很多地方都可以找到idr的身影。
idr机制适用在那些需要把某个整数和特定指针关联在一起的地方。举个例子,在I2C总线中,每个设备都有自己的地址,要想在总线上找到特定的设备,就必须要先发送该设备的地址。如果我们的PC是一个I2C总线上的主节点,那么要访问总线上的其他设备,首先要知道他们的ID号,同时要在pc的驱动程序中建立一个用于描述该设备的结构体。
此时,问题来了,我们怎么才能将这个设备的ID号和他的设备结构体联系起来呢?最简单的方法当然是通过数组进行索引,但如果ID号的范围很大(比如32位的ID号),则用数组索引显然不可能;第二种方法是用链表,但如果网络中实际存在的设备较多,则链表的查询效率会很低。遇到这种清况,我们就可以采用idr机制,该机制内部采用radix树实现,可以很方便地将整数和指针关联起来,并且具有很高的搜索效率。

2.idr源码分析

idr的源码位于linux/idr.h文件,本文使用linux5.5.4内核代码

2.1 idr结构体代码定义与初始化

struct idr {
    struct radix_tree_root  idr_rt;
    unsigned int        idr_base;
    unsigned int        idr_next;
};

其中,radix_tree_root定义为

define radix_tree_root      xarray

idr的初始化

// 静态初始化
#define IDR_INIT(name)  IDR_INIT_BASE(name, 0)

// 动态初始化
static inline void idr_init(struct idr *idr);

2.2 为idr分配内存

void idr_preload(gfp_t gfp_mask);

2.3 分配id号和指针关联

int idr_alloc(struct idr , void ptr, int start, int end, gfp_t);

2.4 通过id号搜索对应的指针

void idr_find(const struct idr , unsigned long id);

2.5 删除id

void idr_remove(struct idr , unsigned long id);

3.IDR使用


misc驱动结构分析

0 条评论 学习总结 Linux驱动 adtxl

1.misc驱动简介

  • misc中文名就是杂项设备\杂散设备,因为现在的硬件设备多种多样,有好些设备不好对他们进行一个单独的分类,所以就将这些设备全部归属于杂散设备,也就是misc设备,例如像adc、buzzer等这些设备一般都归属于misc中。所有的misc类设备都是字符设备,也就是misc类是字符设备中分出来的一个小类。
  • misc类设备在应用层的操作接口:/dev/xxx,设备类对应在/sys/class/misc
  • misc类设备有自己的一套驱动框架,所以我们写一个misc设备的驱动直接利用的是内核中提供的驱动程序框架来实现的。misc驱动框架是对内核提供的原始的字符设备注册接口的一个类层次的封装,很多典型的字符设备都可以归于misc设备,都可以利用misc提供的驱动框架来编写驱动代码,通过misc驱动框架来进行管理。

2.misc驱动框架源码分析

在内核源码中,misc驱动框架源码的实现在:driver/char/misc.c,相应的头文件在:include/linux/miscdevice.h
如果我们添加自己的misc类设备,那么驱动源文件最好在driver/misc这个目录下
先看miscdevice.h的源代码:

/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _LINUX_MISCDEVICE_H
#define _LINUX_MISCDEVICE_H
#include <linux/major.h>
#include <linux/list.h>
#include <linux/types.h>
#include <linux/device.h>

/*
 *  These allocations are managed by device@lanana.org. If you use an
 *  entry that is not in assigned your entry may well be moved and
 *  reassigned, or set dynamic if a fixed value is not justified.
 */

#define PSMOUSE_MINOR       1
#define MS_BUSMOUSE_MINOR   2   /* unused */
#define ATIXL_BUSMOUSE_MINOR    3   /* unused */
/*#define AMIGAMOUSE_MINOR  4   FIXME OBSOLETE */
#define ATARIMOUSE_MINOR    5   /* unused */
#define SUN_MOUSE_MINOR     6   /* unused */
#define APOLLO_MOUSE_MINOR  7   /* unused */
#define PC110PAD_MINOR      9   /* unused */
/*#define ADB_MOUSE_MINOR   10  FIXME OBSOLETE */
#define WATCHDOG_MINOR      130 /* Watchdog timer     */
#define TEMP_MINOR      131 /* Temperature Sensor */
#define APM_MINOR_DEV       134
#define RTC_MINOR       135
#define EFI_RTC_MINOR       136 /* EFI Time services */
#define VHCI_MINOR      137
#define SUN_OPENPROM_MINOR  139
#define DMAPI_MINOR     140 /* unused */
#define NVRAM_MINOR     144
#define SGI_MMTIMER     153
#define STORE_QUEUE_MINOR   155 /* unused */
#define I2O_MINOR       166
#define AGPGART_MINOR       175
#define HWRNG_MINOR     183
#define MICROCODE_MINOR     184
#define IRNET_MINOR     187
#define D7S_MINOR       193
#define VFIO_MINOR      196
#define TUN_MINOR       200
#define CUSE_MINOR      203
#define MWAVE_MINOR     219 /* ACP/Mwave Modem */
#define MPT_MINOR       220
#define MPT2SAS_MINOR       221
#define MPT3SAS_MINOR       222
#define UINPUT_MINOR        223
#define MISC_MCELOG_MINOR   227
#define HPET_MINOR      228
#define FUSE_MINOR      229
#define KVM_MINOR       232
#define BTRFS_MINOR     234
#define AUTOFS_MINOR        235
#define MAPPER_CTRL_MINOR   236
#define LOOP_CTRL_MINOR     237
#define VHOST_NET_MINOR     238
#define UHID_MINOR      239
#define USERIO_MINOR        240
#define VHOST_VSOCK_MINOR   241
#define RFKILL_MINOR        242
#define MISC_DYNAMIC_MINOR  255

struct device;
struct attribute_group;

struct miscdevice  {
    int minor;  // 次设备号
    const char *name;   //设备名称
    const struct file_operations *fops; // 文件操作结构体
    struct list_head list;  // 作为一个链表结点挂接到misc设备维护的一个链表头上去
    struct device *parent;  // 次设备的父设备
    struct device *this_device; // 本设备的device结构体指针
    const struct attribute_group **groups;
    const char *nodename;
    umode_t mode;
};

extern int misc_register(struct miscdevice *misc);
extern void misc_deregister(struct miscdevice *misc);

/*
 * Helper macro for drivers that don't do anything special in the initcall.
 * This helps in eleminating of boilerplate code.
 */
#define builtin_misc_device(__misc_device) \
    builtin_driver(__misc_device, misc_register)

/*
 * Helper macro for drivers that don't do anything special in module init / exit
 * call. This helps in eleminating of boilerplate code.
 */
#define module_misc_device(__misc_device) \
    module_driver(__misc_device, misc_register, misc_deregister)

#define MODULE_ALIAS_MISCDEV(minor)             \
    MODULE_ALIAS("char-major-" __stringify(MISC_MAJOR)  \
    "-" __stringify(minor))
#endif

下面是misc驱动框架的源码实现,misc.c


#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>

/*
 * Head entry for the doubly linked miscdevice list
 */
static LIST_HEAD(misc_list);
static DEFINE_MUTEX(misc_mtx);

/*
 * Assigned numbers, used for dynamic minors
 */
#define DYNAMIC_MINORS 64 /* like dynamic majors */
static DECLARE_BITMAP(misc_minors, DYNAMIC_MINORS);

#ifdef CONFIG_PROC_FS
static void *misc_seq_start(struct seq_file *seq, loff_t *pos)
{
    mutex_lock(&misc_mtx);
    return seq_list_start(&misc_list, *pos);
}

static void *misc_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{
    return seq_list_next(v, &misc_list, pos);
}

static void misc_seq_stop(struct seq_file *seq, void *v)
{
    mutex_unlock(&misc_mtx);
}

static int misc_seq_show(struct seq_file *seq, void *v)
{
    const struct miscdevice *p = list_entry(v, struct miscdevice, list);

    seq_printf(seq, "%3i %s\n", p->minor, p->name ? p->name : "");
    return 0;
}


static const struct seq_operations misc_seq_ops = {
    .start = misc_seq_start,
    .next  = misc_seq_next,
    .stop  = misc_seq_stop,
    .show  = misc_seq_show,
};
#endif

static int misc_open(struct inode *inode, struct file *file)
{
    int minor = iminor(inode);
    struct miscdevice *c;
    int err = -ENODEV;
    const struct file_operations *new_fops = NULL;

    mutex_lock(&misc_mtx);

    list_for_each_entry(c, &misc_list, list) {
        if (c->minor == minor) {
            new_fops = fops_get(c->fops);
            break;
        }
    }

    if (!new_fops) {
        mutex_unlock(&misc_mtx);
        request_module("char-major-%d-%d", MISC_MAJOR, minor);
        mutex_lock(&misc_mtx);

        list_for_each_entry(c, &misc_list, list) {
            if (c->minor == minor) {
                new_fops = fops_get(c->fops);
                break;
            }
        }
        if (!new_fops)
            goto fail;
    }

    /*
     * Place the miscdevice in the file's
     * private_data so it can be used by the
     * file operations, including f_op->open below
     */
    file->private_data = c;

    err = 0;
    replace_fops(file, new_fops);
    if (file->f_op->open)
        err = file->f_op->open(inode, file);
fail:
    mutex_unlock(&misc_mtx);
    return err;
}

static struct class *misc_class;

static const struct file_operations misc_fops = {
    .owner      = THIS_MODULE,
    .open       = misc_open,
    .llseek     = noop_llseek,
};

/**
 *  misc_register   -   register a miscellaneous device
 *  @misc: device structure
 *
 *  Register a miscellaneous device with the kernel. If the minor
 *  number is set to %MISC_DYNAMIC_MINOR a minor number is assigned
 *  and placed in the minor field of the structure. For other cases
 *  the minor number requested is used.
 *
 *  The structure passed is linked into the kernel and may not be
 *  destroyed until it has been unregistered. By default, an open()
 *  syscall to the device sets file->private_data to point to the
 *  structure. Drivers don't need open in fops for this.
 *
 *  A zero is returned on success and a negative errno code for
 *  failure.
 */

int misc_register(struct miscdevice *misc)
{
    dev_t dev;
    int err = 0;
    bool is_dynamic = (misc->minor == MISC_DYNAMIC_MINOR);

    INIT_LIST_HEAD(&misc->list);

    mutex_lock(&misc_mtx);

    if (is_dynamic) {
        int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);

        if (i >= DYNAMIC_MINORS) {
            err = -EBUSY;
            goto out;
        }
        misc->minor = DYNAMIC_MINORS - i - 1;
        set_bit(i, misc_minors);
    } else {
        struct miscdevice *c;

        list_for_each_entry(c, &misc_list, list) {
            if (c->minor == misc->minor) {
                err = -EBUSY;
                goto out;
            }
        }
    }

    dev = MKDEV(MISC_MAJOR, misc->minor);

    misc->this_device =
        device_create_with_groups(misc_class, misc->parent, dev,
                      misc, misc->groups, "%s", misc->name);
    if (IS_ERR(misc->this_device)) {
        if (is_dynamic) {
            int i = DYNAMIC_MINORS - misc->minor - 1;

            if (i < DYNAMIC_MINORS && i >= 0)
                clear_bit(i, misc_minors);
            misc->minor = MISC_DYNAMIC_MINOR;
        }
        err = PTR_ERR(misc->this_device);
        goto out;
    }

    /*
     * Add it to the front, so that later devices can "override"
     * earlier defaults
     */
    list_add(&misc->list, &misc_list);
 out:
    mutex_unlock(&misc_mtx);
    return err;
}
EXPORT_SYMBOL(misc_register);

/**
 *  misc_deregister - unregister a miscellaneous device
 *  @misc: device to unregister
 *
 *  Unregister a miscellaneous device that was previously
 *  successfully registered with misc_register().
 */

void misc_deregister(struct miscdevice *misc)
{
    int i = DYNAMIC_MINORS - misc->minor - 1;

    if (WARN_ON(list_empty(&misc->list)))
        return;

    mutex_lock(&misc_mtx);
    list_del(&misc->list);
    device_destroy(misc_class, MKDEV(MISC_MAJOR, misc->minor));
    if (i < DYNAMIC_MINORS && i >= 0)
        clear_bit(i, misc_minors);
    mutex_unlock(&misc_mtx);
}
EXPORT_SYMBOL(misc_deregister);

static char *misc_devnode(struct device *dev, umode_t *mode)
{
    struct miscdevice *c = dev_get_drvdata(dev);

    if (mode && c->mode)
        *mode = c->mode;
    if (c->nodename)
        return kstrdup(c->nodename, GFP_KERNEL);
    return NULL;
}

// misc_init()函数是misc驱动框架模块注册时的一个初始化函数,只有进行了初始化,我们才能够利用misc提供的框架来进行编写misc设备驱动程序和管理misc类设备。
static int __init misc_init(void)
{
    int err;
    struct proc_dir_entry *ret;

    ret = proc_create_seq("misc", 0, NULL, &misc_seq_ops);
    // 在sys文件系统下创建misc设备类
    misc_class = class_create(THIS_MODULE, "misc");
    err = PTR_ERR(misc_class);
    if (IS_ERR(misc_class))
        goto fail_remove;

    err = -EIO;
    // 注册misc字符设备,主设备是10
    if (register_chrdev(MISC_MAJOR, "misc", &misc_fops))
        goto fail_printk;
    misc_class->devnode = misc_devnode;
    return 0;

fail_printk:
    pr_err("unable to get major %d for misc devices\n", MISC_MAJOR);
    class_destroy(misc_class);
fail_remove:
    if (ret)
        remove_proc_entry("misc", NULL);
    return err;
}
subsys_initcall(misc_init);

Linux内核中链表list文件结构分析

0 条评论 学习总结 Linux 链表 数据结构 adtxl

1. 函数原型

内核中的链表结构如下,只有前后两个指针,没有数据项,可以很方便的构成双向链表

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

1.1. static inline void INIT_LIST_HEAD(struct list_head *list)

运行的时候初始化链表,两个指针都指向结点自己的地址

static inline void INIT_LIST_HEAD(struct list_head *list)
{
    WRITE_ONCE(list->next, list);
    list->prev = list;
}

1.2. static inline void list_add(struct list_head new, struct list_head head);

从指定结点后面插入一个结点,new为要插入的新节点的地址,head为要插入的结点,新结点从head结点后面插入

/**
 * list_add - add a new entry
 * @new: new entry to be added
 * @head: list head to add it after
 *
 * Insert a new entry after the specified head.
 * This is good for implementing stacks.
 */
static inline void list_add(struct list_head *new, struct list_head *head)
{
    __list_add(new, head, head->next);
}

其中,__list_add()函数定义如下,在知道前后结点的情况下,插入结点

/*
 * Insert a new entry between two known consecutive entries.
 *
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 */
static inline void __list_add(struct list_head *new,
                  struct list_head *prev,
                  struct list_head *next)
{
    if (!__list_add_valid(new, prev, next))
        return;

    next->prev = new;
    new->next = next;
    new->prev = prev;
    WRITE_ONCE(prev->next, new);
}

1.3. static inline void list_add_tail(struct list_head new, struct list_head head)

从链表尾部插入结点

/**
 * list_add_tail - add a new entry
 * @new: new entry to be added
 * @head: list head to add it before
 *
 * Insert a new entry before the specified head.
 * This is useful for implementing queues.
 */
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
    __list_add(new, head->prev, head);
}

1.4. static inline void list_del(struct list_head *entry)

两个宏定义,删除下来的prev、next指针指向这两个特殊值,这样设置是为了保证不在链表中的结点项不可访问--对LIST_POISON1和LIST_POISON2的访问都将引起页故障。
/*
 * These are non-NULL pointers that will result in page faults
 * under normal circumstances, used to verify that nobody uses
 * non-initialized list entries.
 */
#define LIST_POISON1  ((void *) 0x100 + POISON_POINTER_DELTA)
#define LIST_POISON2  ((void *) 0x122 + POISON_POINTER_DELTA)
static inline void list_del(struct list_head *entry)
{
    __list_del_entry(entry);
    entry->next = LIST_POISON1;
    entry->prev = LIST_POISON2;
}

其中__list_del_entry()函数定义如下:

/*
 * Delete a list entry by making the prev/next entries
 * point to each other.
 *
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 */
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
    next->prev = prev;
    WRITE_ONCE(prev->next, next);
}

/*
 * Delete a list entry and clear the 'prev' pointer.
 *
 * This is a special-purpose list clearing method used in the networking code
 * for lists allocated as per-cpu, where we don't want to incur the extra
 * WRITE_ONCE() overhead of a regular list_del_init(). The code that uses this
 * needs to check the node 'prev' pointer instead of calling list_empty().
 */
static inline void __list_del_clearprev(struct list_head *entry)
{
    __list_del(entry->prev, entry->next);
    entry->prev = NULL;
}

/**
 * list_del - deletes entry from list.
 * @entry: the element to delete from the list.
 * Note: list_empty() on entry does not return true after this, the entry is
 * in an undefined state.
 */
static inline void __list_del_entry(struct list_head *entry)
{
    if (!__list_del_entry_valid(entry))
        return;

    __list_del(entry->prev, entry->next);
}

1.5. static inline void list_replace(struct list_head old,struct list_head new)

替换链表中的结点,

/**
 * list_replace - replace old entry by new one
 * @old : the element to be replaced
 * @new : the new element to insert
 *
 * If @old was empty, it will be overwritten.
 */
static inline void list_replace(struct list_head *old,
                struct list_head *new)
{
    new->next = old->next;
    new->next->prev = new;
    new->prev = old->prev;
    new->prev->next = new;
}

1.6. static inline void list_replace_init(struct list_head old,struct list_head new)

替换,将被替换的结点初始化为一个新链表

static inline void list_replace_init(struct list_head *old,
                    struct list_head *new)
{
    list_replace(old, new);
    INIT_LIST_HEAD(old);
}

1.7. static inline void list_swap(struct list_head entry1,struct list_head entry2)

交换两个结点

/**
 * list_swap - replace entry1 with entry2 and re-add entry1 at entry2's position
 * @entry1: the location to place entry2
 * @entry2: the location to place entry1
 */
static inline void list_swap(struct list_head *entry1,
                 struct list_head *entry2)
{
    struct list_head *pos = entry2->prev;

    list_del(entry2);
    list_replace(entry1, entry2);
    if (pos == entry1)
        pos = entry2;
    list_add(entry1, pos);
}

1.8. static inline void list_del_init(struct list_head *entry)

删除一项并初始化

/**
 * list_del_init - deletes entry from list and reinitialize it.
 * @entry: the element to delete from the list.
 */
static inline void list_del_init(struct list_head *entry)
{
    __list_del_entry(entry);
    INIT_LIST_HEAD(entry);
}

1.9. static inline void list_move(struct list_head list, struct list_head head)

搬移操作,将原本属于链表的一个结点移动到另一个链表的操作

/**
 * list_move - delete from one list and add as another's head
 * @list: the entry to move
 * @head: the head that will precede our entry
 */
static inline void list_move(struct list_head *list, struct list_head *head)
{
    __list_del_entry(list);
    list_add(list, head);
}

1.10. static inline void list_move_tail(struct list_head list,struct list_head head)

/**
 * list_move_tail - delete from one list and add as another's tail
 * @list: the entry to move
 * @head: the head that will follow our entry
 */
static inline void list_move_tail(struct list_head *list,
                  struct list_head *head)
{
    __list_del_entry(list);
    list_add_tail(list, head);
}

1.11. static inline void list_bulk_move_tail(struct list_head head, struct list_head first, struct list_head *last)

/**
 * list_bulk_move_tail - move a subsection of a list to its tail
 * @head: the head that will follow our entry
 * @first: first entry to move
 * @last: last entry to move, can be the same as first
 *
 * Move all entries between @first and including @last before @head.
 * All three entries must belong to the same linked list.
 */
static inline void list_bulk_move_tail(struct list_head *head,
                       struct list_head *first,
                       struct list_head *last)
{
    first->prev->next = last->next;
    last->next->prev = first->prev;

    head->prev->next = first;
    first->prev = head->prev;

    last->next = head;
    head->prev = last;
}

1.12. static inline int list_is_first(const struct list_head list,const struct list_head head)

判断结点是否为首结点

/**
 * list_is_first -- tests whether @list is the first entry in list @head
 * @list: the entry to test
 * @head: the head of the list
 */
static inline int list_is_first(const struct list_head *list,
                    const struct list_head *head)
{
    return list->prev == head;
}

1.13. static inline int list_is_last(const struct list_head list, const struct list_head head)

判断结点是否为尾结点

/**
 * list_is_last - tests whether @list is the last entry in list @head
 * @list: the entry to test
 * @head: the head of the list
 */
static inline int list_is_last(const struct list_head *list,
                const struct list_head *head)
{
    return list->next == head;
}

1.14. static inline int list_empty(const struct list_head *head)

判断是否是一个空链表

/**
 * list_empty - tests whether a list is empty
 * @head: the list to test.
 */
static inline int list_empty(const struct list_head *head)
{
    return READ_ONCE(head->next) == head;
}

1.15. static inline int list_empty_careful(const struct list_head *head)

/**
 * list_empty_careful - tests whether a list is empty and not being modified
 * @head: the list to test
 *
 * Description:
 * tests whether a list is empty _and_ checks that no other CPU might be
 * in the process of modifying either member (next or prev)
 *
 * NOTE: using list_empty_careful() without synchronization
 * can only be safe if the only activity that can happen
 * to the list entry is list_del_init(). Eg. it cannot be used
 * if another CPU could re-list_add() it.
 */
static inline int list_empty_careful(const struct list_head *head)
{
    struct list_head *next = head->next;
    return (next == head) && (next == head->prev);
}

1.16. static inline void list_rotate_left(struct list_head *head)

翻转链表

/**
 * list_rotate_left - rotate the list to the left
 * @head: the head of the list
 */
static inline void list_rotate_left(struct list_head *head)
{
    struct list_head *first;

    if (!list_empty(head)) {
        first = head->next;
        list_move_tail(first, head);
    }
}

1.17. static inline void list_rotate_to_front(struct list_head list,struct list_head head)

/**
 * list_rotate_to_front() - Rotate list to specific item.
 * @list: The desired new front of the list.
 * @head: The head of the list.
 *
 * Rotates list so that @list becomes the new front of the list.
 */
static inline void list_rotate_to_front(struct list_head *list,
                    struct list_head *head)
{
    /*
     * Deletes the list head from the list denoted by @head and
     * places it as the tail of @list, this effectively rotates the
     * list so that @list is at the front.
     */
    list_move_tail(head, list);
}

1.18. static inline int list_is_singular(const struct list_head *head)

判断一个链表是否只有一项

/**
 * list_is_singular - tests whether a list has just one entry.
 * @head: the list to test.
 */
static inline int list_is_singular(const struct list_head *head)
{
    return !list_empty(head) && (head->next == head->prev);
}

1.19. static inline void list_cut_position(struct list_head list,struct list_head head, struct list_head *entry)

将一个链表拆分为两个

/**
 * list_cut_position - cut a list into two
 * @list: a new list to add all removed entries
 * @head: a list with entries
 * @entry: an entry within head, could be the head itself
 *  and if so we won't cut the list
 *
 * This helper moves the initial part of @head, up to and
 * including @entry, from @head to @list. You should
 * pass on @entry an element you know is on @head. @list
 * should be an empty list or a list you do not care about
 * losing its data.
 *
 */
static inline void list_cut_position(struct list_head *list,
        struct list_head *head, struct list_head *entry)
{
    if (list_empty(head))
        return;
    if (list_is_singular(head) &&
        (head->next != entry && head != entry))
        return;
    if (entry == head)
        INIT_LIST_HEAD(list);
    else
        __list_cut_position(list, head, entry);
}

1.20. static inline void list_cut_before(struct list_head list,struct list_head head,struct list_head *entry)

/**
 * list_cut_before - cut a list into two, before given entry
 * @list: a new list to add all removed entries
 * @head: a list with entries
 * @entry: an entry within head, could be the head itself
 *
 * This helper moves the initial part of @head, up to but
 * excluding @entry, from @head to @list.  You should pass
 * in @entry an element you know is on @head.  @list should
 * be an empty list or a list you do not care about losing
 * its data.
 * If @entry == @head, all entries on @head are moved to
 * @list.
 */
static inline void list_cut_before(struct list_head *list,
                   struct list_head *head,
                   struct list_head *entry)
{
    if (head->next == entry) {
        INIT_LIST_HEAD(list);
        return;
    }
    list->next = head->next;
    list->next->prev = list;
    list->prev = entry->prev;
    list->prev->next = list;
    head->next = entry;
    entry->prev = head;
}

1.21. static inline void list_splice(const struct list_head list,struct list_head head)

连接两个链表

/**
 * list_splice - join two lists, this is designed for stacks
 * @list: the new list to add.
 * @head: the place to add it in the first list.
 */
static inline void list_splice(const struct list_head *list,
                struct list_head *head)
{
    if (!list_empty(list))
        __list_splice(list, head, head->next);
}

1.22. static inline void list_splice_tail(struct list_head list,struct list_head head)

/**
 * list_splice_tail - join two lists, each list being a queue
 * @list: the new list to add.
 * @head: the place to add it in the first list.
 */
static inline void list_splice_tail(struct list_head *list,
                struct list_head *head)
{
    if (!list_empty(list))
        __list_splice(list, head->prev, head);
}

1.23. static inline void list_splice_init(struct list_head list, struct list_head head)

/**
 * list_splice_init - join two lists and reinitialise the emptied list.
 * @list: the new list to add.
 * @head: the place to add it in the first list.
 *
 * The list at @list is reinitialised
 */
static inline void list_splice_init(struct list_head *list,
                    struct list_head *head)
{
    if (!list_empty(list)) {
        __list_splice(list, head, head->next);
        INIT_LIST_HEAD(list);
    }
}

1.24. static inline void list_splice_tail_init(struct list_head list, struct list_head head)

/**
 * list_splice_tail_init - join two lists and reinitialise the emptied list
 * @list: the new list to add.
 * @head: the place to add it in the first list.
 *
 * Each of the lists is a queue.
 * The list at @list is reinitialised
 */
static inline void list_splice_tail_init(struct list_head *list,
                     struct list_head *head)
{
    if (!list_empty(list)) {
        __list_splice(list, head->prev, head);
        INIT_LIST_HEAD(list);
    }
}

2. 使用的宏定义

2.1. LIST_HEAD_INIT

#define LIST_HEAD_INIT(name) { &(name), &(name) }

2.2. LIST_HEAD

#define LIST_HEAD(name) \
    struct list_head name = LIST_HEAD_INIT(name)

2.3. list_entry

#define list_entry(ptr, type, member) \
    container_of(ptr, type, member)

2.4. list_first_entry

#define list_first_entry(ptr, type, member) \
    list_entry((ptr)->next, type, member)

2.5. list_last_entry

#define list_last_entry(ptr, type, member) \
    list_entry((ptr)->prev, type, member)

2.6. list_first_entry_or_null

#define list_first_entry_or_null(ptr, type, member) ({ \
    struct list_head *head__ = (ptr); \
    struct list_head *pos__ = READ_ONCE(head__->next); \
    pos__ != head__ ? list_entry(pos__, type, member) : NULL; \
})

2.7. list_next_entry

#define list_next_entry(pos, member) \
    list_entry((pos)->member.next, typeof(*(pos)), member)

2.8. list_prev_entry

#define list_prev_entry(pos, member) \
    list_entry((pos)->member.prev, typeof(*(pos)), member)

2.9. list_for_each

#define list_for_each(pos, head) \
    for (pos = (head)->next; pos != (head); pos = pos->next)

2.10. list_for_each_prev

#define list_for_each_prev(pos, head) \
    for (pos = (head)->prev; pos != (head); pos = pos->prev)

2.11. list_for_each_safe

#define list_for_each_safe(pos, n, head) \
    for (pos = (head)->next, n = pos->next; pos != (head); \
        pos = n, n = pos->next)

2.12. list_for_each_prev_safe

#define list_for_each_prev_safe(pos, n, head) \
    for (pos = (head)->prev, n = pos->prev; \
         pos != (head); \
         pos = n, n = pos->prev)

2.13. list_for_each_entry

#define list_for_each_entry(pos, head, member)              \
    for (pos = list_first_entry(head, typeof(*pos), member);    \
         &pos->member != (head);                    \
         pos = list_next_entry(pos, member))

2.14. list_for_each_entry_reverse

#define list_for_each_entry_reverse(pos, head, member)          \
    for (pos = list_last_entry(head, typeof(*pos), member);     \
         &pos->member != (head);                    \
         pos = list_prev_entry(pos, member))

2.15. list_prepare_entry

#define list_prepare_entry(pos, head, member) \
    ((pos) ? : list_entry(head, typeof(*pos), member))

2.16. list_for_each_entry_continue

#define list_for_each_entry_continue(pos, head, member)         \
    for (pos = list_next_entry(pos, member);            \
         &pos->member != (head);                    \
         pos = list_next_entry(pos, member))

2.17. list_for_each_entry_from_reverse

#define list_for_each_entry_from_reverse(pos, head, member)     \
    for (; &pos->member != (head);                  \
         pos = list_prev_entry(pos, member))

2.18. list_for_each_entry_safe

#define list_for_each_entry_safe(pos, n, head, member)          \
    for (pos = list_first_entry(head, typeof(*pos), member),    \
        n = list_next_entry(pos, member);           \
         &pos->member != (head);                    \
         pos = n, n = list_next_entry(n, member))

2.19. list_for_each_entry_safe_continue

#define list_for_each_entry_safe_continue(pos, n, head, member)         \
    for (pos = list_next_entry(pos, member),                \
        n = list_next_entry(pos, member);               \
         &pos->member != (head);                        \
         pos = n, n = list_next_entry(n, member))

2.20. list_for_each_entry_safe_from

#define list_for_each_entry_safe_from(pos, n, head, member)             \
    for (n = list_next_entry(pos, member);                  \
         &pos->member != (head);                        \
         pos = n, n = list_next_entry(n, member))

2.21. list_for_each_entry_safe_reverse

#define list_for_each_entry_safe_reverse(pos, n, head, member)      \
    for (pos = list_last_entry(head, typeof(*pos), member),     \
        n = list_prev_entry(pos, member);           \
         &pos->member != (head);                    \
         pos = n, n = list_prev_entry(n, member))

2.22. list_safe_reset_next

#define list_safe_reset_next(pos, n, member)                \
    n = list_next_entry(pos, member)

Linux-系统操作

0 条评论 学习总结 Linux adtxl

1.帮助命令

  • man
    man是manual的缩写
    用法: man 命令
    man 也是一条命令,分为9章,可以使用man命令获得man的帮助,如man 7 man

  • help
    内部命令使用help帮助,help 命令
    外部命令使用help帮助,命令 --help
    使用type 命令可以查看是内部命令还是外部命令

  • info
    info帮助比help更详细

2.文件命令

  • pwd命令
    显示当前目录的绝对路径

  • ls命令
    查看当前目录下的文件

    • 基本语法:
      ls [选项,选型] 参数......
    • 常用选项
      -l 长格式显示文件
      -a 显示隐藏文件
      -r 逆序显示
      -t 按照时间顺序显示
      -R 递归显示
  • cd命令
    更改当前的操作目录

    • 常用操作:
      cd - :返回上一目录
  • mkdir命令
    建立目录

    • 常用选项:
      -p 建立多级目录
  • rmdir命令
    删除空目录

  • cp命令
    复制文件和目录

    • 基本语法:
      cp [选项] 文件路径
      cp [选项] 文件... 路径
    • 常用选项:
      -r 复制目录,不加选项只能复制文件
      -p 复制时保留用户、权限、时间等文件属性
      -a 等同于-dpR,显示复制过程
  • mv命令
    移动或者重命名文件

    • 基本语法:
      mv [选项] 源文件 目标文件
      mv [选项] 源文件 目录
  • rm命令
    删除文件

    • 常用选项:
      -r 删除目录,非空的
      -f 删除文件不进行提示
  • 通配符

    • 定义:shell 内建的符号
    • 用途:操作多个相似的文件
    • 常用的通配符:
      * 匹配任何字符串
      ? 匹配一个字符串
      [xyz] 匹配xyz任意一个字符
      [a-z] 匹配一个范围
      [!xyz] 不匹配

3. 文本查看命令

  • cat 文本内容显示到终端
  • head 查看文件开头
  • tail 查看文件结尾
    • 常用参数:-f 文件内容更新后,显示信息同步更新
  • wc 统计文件内容信息
  • more
  • less

4.打包与压缩命令

  • 打包命令
    tar命令是Linux中的备份命令,在打包完成后,需要对文件进行压缩,压缩的命令是gzip和bzip2.
    经常使用的扩展名是.tar.gz .tar.bz2 .tgz .tbz2
    • 常用选项:
      c 打包
      x 解包
      f 指定操作类型为文件
  • 压缩和解压缩
    可以先使用tar命令打包,再单独使用命令gzip和bzip2命令。但在日常的使用中,通常和tar命令配合使用
    • 常用选项:
      -z: gzip格式压缩和解压缩
      -j: bzip2格式压缩和解压缩

5.Vi编辑器

进入vim后即为正常模式,可以复制粘贴。按i进入插入模式,可以进行文本的输入。从插入模式退出,按ESC进入正常模式,然后输入:或者\进入命令模式,在命令行下输入:wq,:q可退出。

  • 正常模式
    进入其他模式的转换命令

    • i 进入插入模式
    • v 进入可视化模式
    • : 进入命令模式
    • esc 从其他模式回到正常模式
      基本操作:
      使用h j k l控制上下左右的移动,一些基本操作
    • y 复制
      一般都是按行复制,使用yy命令,使用数字加yy可以复制多行,使用y$可以复制从光标到行尾全部内容
    • d 剪切
      dd剪切一整行
    • p 粘贴
    • u 撤销
    • ctrl+r 重做,把撤销指令重做
    • x 删除单个字符
    • r 替换单个字符
    • G 定位指定的行
      数字加G定位到指定行
    • ^ 定位到行首
    • $ 定位到行尾
  • 命令模式

    • :w 写入
    • :q 退出
    • :! 执行shell命令
    • :s 替换
      使用方法s/old/new,只是用s只替换光标所在行的内容,使用%s可替换所有行的第一个字符。使用%s/old/new/g可以替换所有,global
      3,5s/old/new是指替换3到5行的
    • / 查找
      使用n查看下一个查找到的内容,使用shift+n查看上一个
    • :set 设置命令
      :set nu, :set nonu
  • 可视模式
    进入可视模式的方式

    • v 字符可视模式
    • V 行可视模式
    • ctrl+v 块可视化模式

6.用户和用户组管理及密码管理

  • 用户管理常用命令
    • useradd 新建用户
      useradd 用户名用户是否存在,
      使用 id 用户名 可以知道用户是否存在
    • userdel 删除用户
      userdel 用户名即可删除用户,但会保留用户的家目录
      使用-r参数可以删除用户目录
    • passwd 修改用户密码
    • usermod 修改用户属性
      可以修改用户家目录、用户组等信息
    • chage 修改用户属性
      可以修改用户的生命周期
  • 用户组管理命令
    • groupadd 新建用户组
    • groupdel 删除用户组
  • su和sudo命令
    • su 切换用户
      su - USERNAME
    • sudo 以其他用户身份执行命令
    • visudo 设置需要使用sudo的用户(组)
  • 用户和用户组配置文件
    /etc/passwd/
    /etc/shadow/
    /etc/group/

7.文件权限

  • 文件类型:
    - 普通文件
    d 目录文件
    b 块特殊文件
    c 字符特殊文件
    l 符号链接(类似Windows快捷方式)
    f 命名管道
    s 套接字文件

  • 文件权限的表示
    字符权限的表示法:
    r 读
    w 写
    x 执行
    数字权限的表示法:
    r = 4
    w = 2
    x = 1
    如 rw-r-xr--意为
    rw- 文件属主的权限
    r-x 文件属组的权限
    r-- 其它用户的权限

  • 文件权限的修改
    root用户权限不受限

  • chmod 更该文件、目录权限
    字符表示法:
    u g o a参数表示用户属主、属组、其他用户、和全部
    u=x,u+x,u-x设置、增加、减少权限
    chmod u+x /tmp/testfile
    数字表示法:
    chmod 755 /tmp/testfile

  • chown 更改属主、属组

  • chgrp 可以单独改属组,不常用
    使用ctrl+r,可以查找历史命令