linux驱动开发经验逐步积累2

注:笔记多少会有问题,多多包涵。只是作为一个记录而已


1. cdev_add的核心思想
cdev_add允许添加一个字符设备到内核,其核心是kobj_map,也可以添加一个字符设备集合,他可以包含count个连续的子设备号,此时dev_t dev为该字符设备集的base设备号,如cdev_add(cdev, 81, 256)。
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
    p->dev = dev;
    p->count = count;
    return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}
kobj_map核心是构建一个struct probe包含一个字符设备集合(rang可以是1,最大是256个),这说明其他81号字符设备,均落在一个probe中,即只是一个cdev存在,这使得操作设备节点时,所以major为81的设备都是同一个cdev,使得最终操作的file ops都是一致的。。

当然对于上述将256个设备集合在一个cdev中,也可以通过cdev_add(cdev, dev_t, 1)来单个注册,只是每次kobj_map时都会存在一个probe,256次就是256个probe list,这就允许在操作每个设备节点有自己的cdev以及各自独立的file ops操作。在linux驱动中,同一主设备号的设备,可能最终面临的file ops接口会有不同。

2 linux file多次open问题
那么有没有不同的文件描述符使用同一个 file 结构体呢?答案是肯定的, 使用 Linux 中的 dup 或 dup2 系统调用即可复制文件描述符,这样便可以使两个文件描述符指向同一个 file 结构体。这时,如果使用这两个文件描述符进行的读操作是不独立的。
假设 fd1 和 fd2 两个文件描述符都指向同一个 file 结构体,当使用 close 系统调用关闭 fd1后, fd2 仍可以使用。 这是因为在 file 结构体中还维护者一个引用计数 count,当 close(fd1) 后,count 减 1,只有在 count 为 0 时,内核才会真正的释放 file 结构体真正关闭文

3 linux file中的f_ops表示当前file被读写后产生的位置偏移量,每次对文件的读写操作都是从f_ops开始即每次读写多少个字节后f_ops都会自动偏移,使得下次开始时就是从最新的位置开始读写数据,即在读写都是基于上次文件f_ops所在的位置开始。但实际使用中可以通过lseek来修改该f_ops的位置,便于读写。
用户空间 内核
open() sys_open(), filp_open()
close() sys_close(), filp_close()
read() sys_read(), filp_read()
write() sys_write(), filp_write()

4cscope -Rbq创建cscope索引库

5 "C:\Program Files (x86)\PuTTY\putty.exe" -load xxx -ssh -l zezhigon -pw gzz@19890908188

“[]”表示option,因此可以定义一个只有node name的空节点

7mount命令挂载不同的文件系统
mount 挂载的文件系统类型 设备 挂载的目录

8 linux内核中page和pfn,以及内核physical address和virtual address之间的关系。
page_to_pfn:通过page找到所属的物理页帧号。
pfn_to_page::通过物理页帧号找到对应的page。//一般page的属性中包含pfn.
virt_to_page:通过内核虚拟地址找到所属的page
page_to_phys:通过page中的pfn转为物理地址,一般是以pfn<
page_address:通过page找到所属页对应的虚拟地址(896M地段内存线性,高端内存映射通过hash表维护从而获取虚拟地址)

9 linux errno宏可以用于处理当前错误的原因,常用于fopen等操作

10 在init.rc中定义 mount debugfs debugfs /sys/kernel/debug,挂载debugfs(ramfs)文件系统到/sys/kernel/debug所在的super root根目录。
mount selinux selinux /sys/fs/selinux

12 每个挂载的文件系统super inode根节点均为/,一般挂载点如/dev, /sys等位置的inode和dentry都是属于rootfs文件系统,当ramfs文件系统或者sysfs选择其作为挂载点时,会新建一个super inode的root,也就是super_block,来作为新的文件系统入口inode,其特点是不存在dentry的概念。只有当在/下开始建立file或者dir时,才会根据super inode的fops来建立新的inode。即根文件系统以“/” super inode的inode_ops分别建立dev和sys这两个目录,然后当mount挂载sysfs和tmpfs系统到/dev,/sys下并创建各自的super indo,可认为/即为该super inode root,如/dev/fb0,/sys/class
   当处理当挂载点时,他本质还是前一级别的文件系统,更具inode/dentry中挂载点的属性,来判断是非属于挂载点,如果是的话将挂载文件的root inode/dentry修正为当前的目录path_look_up的起点,继续查看比如/dev/fb0,fb0在新的文件系统中的dentry,如果没有则新建dentry,同时根据parent的inode ops来创建dentry对应的inode,并将两者绑定,从而在新的文件系统下完成一个新的目录或者文件的创建。

13 makefile gcc 链接库问题
-L: “链接”的时候,去找的目录,也就是所有的 -lFOO 选项里的库,都会先从 -L 指定的目录去找,然后是默认的地方。编译时的-L选项并不影响环境变量LDLIBRARYPATH,-L只是指定了程序编译连接时库的路径,并不影响程序执行时库的路径,系统还是会到默认路径下查找该程序所需要的库,如果找不到,还是会报错,类似cannot open shared object file。

-rpath-link:这个也是用于“链接”的时候的,例如你显示指定的需要la,但是 liba.so 本身是需要libb.so 的,后者你并没有显示指定lb,而编译器知道是 liba.so 引用到它,这个时候,对libb.so会间接的先从 -rpath-link 给的路径里查找。

-rpath: “运行”的时候,去找的目录。运行的时候,要找 .so 文件,会从这个选项里指定的地方去找。对于交叉编译,交叉编译链接器需已经配置 --with-sysroot 选项才能起作用。也就是说,-rpath指定的路径会被记录在生成的可执行程序中,用于运行时查找需要加载的动态库。 -rpath-link 则只用于链接时查找

`-lARCHIVE'
`--library=ARCHIVE'
增加一个档案文件ARCHIVE到连接的文件列表中.这个选项可以被多次使用. 'ld'会为每一个指定的
ARCHIVE搜索它的路径列表,寻找`libARCHIVE.a'

对于支持共享库的系统, 'ld'可能还会搜索扩展名不是'.a'库.特别的,在ELF和SunOS系统上,'ld'会
在搜索带有'.a'扩展名的库前搜索带'.so'扩展名的库.

-llibrary
-l library
           Search the library named library when linking.  (The second alternative with the library as a separate argument is only for POSIX compliance and is not recommended.)
           It makes a difference where in the command you write this option; the linker searches and processes libraries and object files in the order they are specified.
           Thus, foo.o -lz bar.o searches library z after file foo.o but before bar.o.  If bar.o refers to functions in z, those functions may not be loaded.
           The linker searches a standard list of directories for the library, which is actually a file named liblibrary.a.  The linker then uses this file as if it had been specified precisely by name.
         The directories searched include several standard system directories plus any that you specify with -L.
    Normally the files found this way are library files---archive files whose members are object files.  The linker handles an archive file by scanning through it for members which define symbols that have so far been referenced but not defined.  But if the file that is found is an ordinary object file, it is linked in the usual fashion.  The only difference between using an -l option and specifying a file name is that -l surrounds library with lib and .a and searches several directories.

14 sysfs属性文件中的__ATTR宏定义
 #define __ATTR(_name,_mode,_show,_store) { /
     .attr = {.name = __stringify(_name), .mode = _mode }, /
     .show = _show,     /
     .store = _store,     /
    }

15 linux虚拟终端中执行命令行的响应过程
首先是执行/bin/sh可执行文件来作为终端命令行接收与处理的入口。以及创建新的进程来执行命令行传入的可执行文件
/bin/sh作为一个终端入口,本质也是一个运行着的程序,一般由内核启动的第一个init进程来fork出子进程后启动:
 fork     execve                 execve                 fork                       execve 
init --> init --> /sbin/getty --> /bin/login --> /bin/login --> /bin/sh


16 write和read等linux中的系统调用都属于glibc库,内部采用SIG软中断方式来进入内核态后实现sys_write和sys_read等的系统调用。对于带缓存的fwrite和fread函数,也是由glibc来实现的,源码以_IO_new_fopen->_IO_file_open为主,本质是构建一个FILE来表示系统调用open返回的fd。即fopen/fread本质只是对于系统调用的一层封装而已。
而对于Android系统而言,一般使用自带的Bionic libc库来实现系统调用,生成libc.so/libc_bionic.a等等(前者属于lib目录要打包到system中去的,后者主要用于中间编译使用,直接被打包进可执行文件)
(部分可能还需要使用的libc++.so的库源码位于android/external/libcxx目录下)
系统调用在传递参数使,是将参数通过一组ebx/ecx/edx等参数作为函数形参压栈后再调用具体的系统调用sys_open函数,每个不同的系统调用处理函数,会有不同的形参个数,所以每次调用前这个形参入栈过程都是需要有SAVE_ALL这个宏来完成的。

17 Makefile多目标依赖关系与目标多依赖
obj0 obj1 : objxxx
在make处理时,如果obj依赖如obj1则执行objxxx的依赖关系,即每个目标依赖的是同一个事务,会被解析为
obj0 : objxxx
obj1 : objxxx
其中命令行中出现的$@是依次从obj0 obj1这个目标列表中取值

obj:obj0
obj:obj1
是等价于obj:obj0 obj1

18 静态库lib.a的本质
静态库只是一堆object对象的集合,使用ar命令可以将编译产生的.o文件打包成.a静态库。
生成静态库只有编译,而没有链接故可以不指定他所依赖的一些动态库;而动态库在生成的时候时既有编译的动作也有链接的动作。静态库在被别的程序(可执行程序或是动态库)链接的时候,链接器会将程序中使用到函数的代码从静态库文件中拷贝到应用程序中的。
静态库生成时是没有链接的,所以生成它的时候不需要指定它所依赖的外部库;动态库生成时是有链接动作的,那当然就要指定它依赖哪些外部库了.
链接只在生成可执行文件和动态库时会做ld相关的操作


19 make处理makefile的基本流序,类似gcc编译源文件一样
   当执行make命令时,会读取对应的makefile文件,然后按照顺序进行预处理:包括include其他的makefile文件,各种变量的赋值处理,各种隐式编译规则的生成处理,直到最后全部预处理完成才会真正开始执行目标文件的编译输出。其中目标所依赖文件的编译规则输出可以定义在makefile的各种位置,不需要一定定义在前面目标执行规则的前面,不具有前后关联性。

20 在Makefile文件中define自定义的函数不仅可以处理变量参数,还可以定义相关的目标依赖编译规则:
2198 define copy-one-file
2199 $(2): $(1) | $(ACP)
2200     @echo "Copy: $$@"
2201     $$(copy-file-to-target)
2202 endef

21 linux shell输出重定向,将命令输出到新的文件或者设备节点
格式:
command-line1 [1-n] > file或文件操作符或设备文件

上面命令意思是:将一条命令执行结果(标准输出,或者错误输出,本来都要打印到屏幕上面的) 重定向其它输出设备(文件,打开文件操作符,或打印机等等)1,2分别是标准输出,0表示错误输出。
其中对文件而言>>添加在文件尾部,>会清空文件后将命令输出写入到新的文件之中。

22 makefile中的变量申明/赋值以及取值引用
变量申明:
A := foo
变量引用$是取变量所代表的值:
B := $(A)
$(B)则为foo

变量的嵌套
假设foo := foo.o
$($(A))=$(foo)=foo.o

foo := $(A).o则foo变量赋值为foo.o

23 Makefile中的foreach函数
foreach 函数和别的函数非常的不一样。因为这个函数是用来做循环用的,Makefile中的foreach函数几乎是仿照于Unix标准Shell (/bin/sh)中的for语句,或是C-Shell(/bin/csh)中的foreach语句而构建的。它的语法是:

  $(foreach ,,)

这个函数的意思是,把参数;中的单词逐一取出放到参数;所指定的变量中,然后再执行< text>;所包含的表达式。每一次;会返回一个字符串,循环过程中,;的所返回的每个字符串会 以空格分隔,最后当整个循环结束时,;所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。

所以,;最好是一个变量名,;可以是一个表达式,而;中一般会使用;这个参数来依次枚举;中的单词。举个例子:
names := a b c d
files := $(foreach n,$(names),$(n).o)

上面的例子中,$(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次根据“$(n)”计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,所以$(files)的值是“a.o b.o c.o d.o”。
注意,foreach中的;参数是一个临时的局部变量,foreach函数执行完后,参数;的变量将不在作用,其作用域只在

24 makefile中的sort函数遇到重复字符是只保留一个
$(sort, a b a c )输出为a b c 

25 Makefile中的subst和patsubst区别
两者都有字符替换的能力,后者在匹配字符时当没有出现通配符合%时,只有当TEXT完全匹配pattern才会被replace中替换。而subst具备替换TEXT中部分字符的能力,patsubst在非通配%模式下不具备部分替换。

25 Makefile中的ifdef/endif宏
ifdef用来检测当前变量是否为空值还是已经被赋值,而不是C语言的ifdef是否存在该宏,当检测的变量为空值时,直接else处理

26 使用export导入环境变量时等号两边是不能有空格的
即export PATH=xxxx:xxxxx
否则会报错:export `=' not a valid identifier的一般原因

27 Makefile中添加调试信息打印时一是通过自带的info,error,warning函数来实现,二是定义target通过echo来实现

28 Makefile中的CURDIR变量值, 表示当前make执行makefile时所处的工作目录,可通过make -C切换,默认不指定时工作目录为当前执行make操作时所在的目录
当执行make -C xxx -f $(CURDIR)/Makeflie操作时,会读入xxx所在路径下的Makefile文件,文件名可以通过-f制定,无指定时默认读入Makefile文件。CURDIR在读入-C xxx时自动由make工具生成,即-C指定的 目录绝对路径xxx所在,供当前Makefile中作为变量来使用。

29当执行可以执行bin文件出现bin:
整个环境变量下的确无该bin可执行文件存在报command not found

bash bin: No such file or directory
一方面还可能是该文件在执行时找不到程序runtime时所需要的so动态库,报No such file or directory的错误,另外就是无法定位到可执行文件elf中定义的ld-linux.so程序加载器so库所在绝对路径,即内核无法加载ld-linux.so来为程序运行做准备。
在gcc中使用ld链接选项时,需要在选项前面加上前缀-Wl,-rpath, xxxx1  -Wl,-rpath, xxxx2

30  Linux下elf文件的动态链接器是ld-linux.so,区别于gcc所使用的静态链接器ld对于可执行文件查找路径先PATH下后当前目录下
每个ELF格式的可执行文件都可以配备ld-linux.so,该文件在程序运行时被加载,但必须要通过可执行文件告知其绝对路径所在,该文件是不会依赖其他库文件。
从上一小节中发现有一个专门的节区.interp存放有动态链接器,但是这个节区为什么叫做.interp(interpeter)呢?因为当shell解释器或者其他父进程通过exec启动我们的程序时,系统会先为ld-linux创建内存映像,然后把控制权交给ld-linux,之后ld-linux负责为可执行程序提供运行环境,负责解释程序的运行,因此ld-linux也叫做dynamic loader(或intepreter)(关于程序的加载过程请参考资料

31 Makefile中所有以$打头的单词都会被解释成Makefile中的变量。如果你需要调用shell中的变量(或者正则表达式中锚定句位$),都需要加两个$符号($$)来表示shell的变量
PATH="/data/"
all:
  echo ${PATH}
  echo $$PATH
例子中的第一个${PATH}引用的是Makefile中的变量,而不是shell中的PATH环境变量,后者引用的事Shell中的PATH环境变量

32 linux shell脚本以及各种可执行文件运行环境是继承至运行它的进程环境。
对于Linux而言,应用程序的默认工作目录就不同了,它是默认是继承启动它的进程的工作目录的,也就是说,如果进程是在其它目录下启动,那应用程序的工作目录默认就在其它的目录下。这样的话使用相对路径访问文件就有很大的不确定性:我们永远也不能保证,用户一定是从应用程序所在的目录启动!

33 Makefile VPATH变量的作用:
make在查找依赖文件时先是在当前工作目录下查找,然后再去VPATH定义的绝对路径下去继续查找依赖文件,最终找到依赖文件后根据是否有VPATH参与将依赖文件的绝对路径赋值给$<变量,一般 $<值为相对当前工作目录的路径所在。


34 gcc -E/-S/-c
-E: 预处理输出.i文件
-S: 编译,输出为.s文件
-c: 汇编,包括预处理和编译,将.s汇编后变为2进制机器码,默认输出为.o目标文件,无链接
默认不指定时都会经历以上过程并加入链接操作
gcc a.c -o a.bin
gcc a.c//输出a.out
以上两种方式都会一次预处理/编译/汇编/链接等4个步骤的操作,直到输出可执行文件为止。





你可能感兴趣的:(linux)