标签 linux 下的文章
广告位B招租:1
 2021-08-28T00:44:49.857842    |      内核学习    |     AilsonJack    |     暂无评论    |     73 views
根据上一章《Linux内核与驱动学习记录-字符设备驱动程序框架》的内容,这一章编写了一个例程,作为实验进行说明,加深对字符设备驱动程序开发步骤的理解。实验代码如下:Makefile内容如下:将实验代码编译之后的chrdev_frame.ko,下载到板子。加载内核模块,执行命令:sudo insmod chrdev_frame.ko卸载内核模块,执行命令:sudo rmmod chrdev_frame
Linux内核学习驱动开发嵌入式 阅读全文»
 2021-07-31T21:24:33.581477    |      内核学习    |     AilsonJack    |     暂无评论    |     111 views
字符设备的驱动程序开发步骤大致都是差不多的,这里绘制了一张图来形象的反应字符设备驱动程序的关键步骤:我们创建一个字符设备的时候,首先要得到一个设备号,分配设备号的途径有静态分配和动态分配;拿到设备的唯一 ID,我们需要实现 file_operation 并保存到 cdev 中,实现 cdev 的初始化;然后我们需要将我们所做的工作告诉内核,使用 cdev_add() 注册 cdev;最后我们还需要创建设备节点,以便我们后面调用 file_operation接口。注销设备时我们需释放内核中的 cdev,归还申请的设备号,删除创建的设备节点。1.字符设备的定义Linux 内核提供了两种方式来定义字符设备:第一种方式,就是我们常见的变量定义;第二种方式,是内核提供的动态分配方式,调用该函数之后,会返回一个 struct cdev 类型的指针,用于描述字符设备。第二种方式定义的字符设备,可以通过cdev_del函数来释放占用的内存。2.设备号的申请和归还2.1.设备号的静态申请register_chrdev_region 函数用于静态地为一个字符设备申请一个或多个设备编号。int register_chrdev_region(dev_t from, unsigned count, const char *name);参数:from:dev_t 类型的变量,用于指定字符设备的起始设备号,如果要注册的设备号已经被其他的设备注册了,那么就会导致注册失败;count:指定要申请的设备号个数, count 的值不可以太大,否则会与下一个主设备号重叠;name:用于指定该设备的名称,我们可以在/proc/devices 中看到该设备。返回值:返回 0 表示申请成功,失败则返回错误码。2.2.设备号的动态申请使用 register_chrdev_region 函数时,都需要去查阅内核源码的 Documentation/devices.txt 文件,这就十分不方便。因此,内核又为我们提供了一种能够动态分配设备编号的方式: alloc_chrdev_region。调用 alloc_chrdev_region 函数,内核会自动给我们分配一个尚未使用的主设备号。我们可以通过命令“cat /proc/devices”查询内核分配的主设备号。int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);参数:dev:指向 dev_t 类型数据的指针变量,用于存放分配到的设备编号的起始值;baseminor:次设备号的起始值,通常情况下,设置为 0;count:指定要申请的设备号个数, count 的值不可以太大,否则会与下一个主设备号重叠;name:用于指定该设备的名称,我们可以在/proc/devices 中看到该设备。返回值:返回 0 表示申请成功,失败则返回错误码。2.3.设备号的申请(静态和动态都支持)除了register_chrdev_region函数能够静态申请设备号,alloc_chrdev_region函数能够动态申请设备号之外,内核还提供了register_chrdev 函数用于分配设备号。该函数是一个内联函数,它不仅支持静态申请设备号,也支持动态申请设备号,并将主设备号返回。register_chrdev 函数原型如下:参数:major:用于指定要申请的字符设备的主设备号,等价于 register_chrdev_region 函数,当设置为 0 时,内核会自动分配一个未使用的主设备号;name:用于指定字符设备的名称;fops:用于操作该设备的函数接口指针。返回值:主设备号。我们从以上代码中可以看到,使用 register_chrdev 函数向内核申请设备号,同一类字符设备(即主设备号相同),会在内核中申请了256 个,通常情况下,我们不需要用到这么多个设备,这就造成了极大的资源浪费。因此通常情况下,并不使用该函数。2.4.设备号的归还当我们删除字符设备时候,我们需要把分配的设备编号交还给内核,对于使用 register_chrdev_region 函数以及 alloc_chrdev_region 函数分配得到的设备编号,可以使用 unregister_chrdev_region 函数将分配得到的设备号归还给内核。void unregister_chrdev_region(dev_t from, unsigned count);参数:from:指定需要注销的字符设备的设备编号起始值,我们一般将定义的 dev_t 变量作为实参;count:指定需要注销的字符设备编号的个数,该值应与申请函数的 count 值相等,通常采用宏定义进行管理。3.字符设备的初始化cdev_init()函数主要将file_operations结构体和我们的字符设备结构体相关联。void cdev_init(struct cdev *cdev, const struct file_operations *fops);参数:cdev:struct cdev 类型的指针变量,指向需要关联的字符设备结构体;fops:file_operations 类型的结构体指针变量,一般将实现操作该设备的结构体 file_operations 结构体作为实参。4.字符设备的注册和移除4.1.字符设备的注册cdev_add 函数用于向内核的 cdev_map 散列表添加一个新的字符设备。int cdev_add(struct cdev *p, dev_t dev, unsigned count);参数:p:struct cdev 类型的指针,用于指定需要添加的字符设备;dev:dev_t 类型变量,用于指定设备的起始编号;count:指定注册多少个设备。返回值:0或者错误码。4.2.字符设备的移除从内核中移除某个字符设备,则需要调用 cdev_del 函数。从系统中删除 cdev, cdev 设备将无法再打开,但任何已经打开的 cdev 将保持不变,即使在 cdev_del 返回后,它们的 fops 仍然可以调用。void cdev_del(struct cdev *p);参数:p:将已经注册的字符设备结构体的地址作为实参传递进去,就可以从内核中移除该字符设备了。5.设备节点的创建和销毁5.1.设备节点的创建可以在代码中使用device_create函数创建设备节点。参数:class:指向这个设备应该注册到的 struct 类的指针;parent:指向此新设备的父结构设备(如果有)的指针;devt:要添加的字符设备的设备号;drvdata:要添加到设备进行回调的数据;fmt:输入设备名称。返回值:成功时返回 struct device 结构体指针, 错误时返回 ERR_PTR()。5.2.设备节点的销毁可以使用device_destroy函数来删除 device_create 函数创建的设备节点。void device_destroy(struct class *class, dev_t devt);参数:class:指向注册此设备的 struct 类的指针;devt:以前注册设备时,使用的设备号。5.3.使用mknod命令创建设备节点除了使用代码创建设备节点,还可以使用 mknod 命令创建设备节点。用法: mknod 设备名 设备类型 主设备号 次设备号当类型为”p”时可不指定主设备号和次设备号,否则它们是必须指定的。如果主设备号和次设备号以”0x”或”0X”开头,它们会被视作十六进制数来解析;如果以”0”开头,则被视作八进制数;其余情况下被视作十进制数。可用的设备类型包括:b:创建 (有缓冲的) 区块特殊文件;c,u:创建 (没有缓冲的) 字符特殊文件;p:创建先进先出 (FIFO) 特殊文件。如: mkmod /dev/test c 2 0创建一个字符设备/dev/test,其主设备号为 2,次设备号为 0。当我们使用上述命令,创建了一个字符设备文件时,实际上就是创建了一个设备节点 inode 结构体,并且将该设备的设备编号记录在成员i_rdev,将成员 f_op 指针指向了 def_chr_fops 结构体。这就是 mknod 负责的工作内容。inode 上的 file_operation 并不是自己构造的 file_operation,而是字符设备通用的 def_chr_fops,那么自己构建的 file_operation 等在应用程序调用 open 函数之后,才会绑定在文件上。
Linux内核学习驱动开发嵌入式 阅读全文»
 2021-06-30T20:16:30.276882    |      内核学习    |     AilsonJack    |     暂无评论    |     76 views
1.Linux设备分类按照读写存储数据方式,我们可以把Linux设备分为以下几种:字符设备、块设备和网络设备。字符设备: 指应用程序按字节/字符来读写数据的设备。它通常不支持随机存取数据。字符设备在实现时,大多不使用缓存器,系统直接从设备读取/写入每一个字符。块设备: 通常支持随机存取和寻址,并使用缓存器。它与字符设备不同之处就是,是否支持随机存储。字符型是流形式,逐一存储。数据的读写只能以块的倍数进行。网络设备: 是一种特殊设备,它并不存在于/dev 下面,主要用于网络数据的收发。Linux 内核中处处体现面向对象的设计思想,为了统一形形色色的设备, Linux 系统将设备分别抽象为 struct cdev, struct block_device,struct net_devce 三个对象,具体的设备都可以包含着三种对象从而继承和三种对象属性和操作,并通过各自的对象添加到相应的驱动模型中,从而进行统一的管理和操作。2.字符设备抽象Linux 内核中将字符设备抽象成一个具体的数据结构 (struct cdev), 我们可以理解为字符设备对象,cdev 记录了字符设备的相关信息(设备号、内核对象),字符设备的打开、读写、关闭等操作接口(file_operations)。在我们想要添加一个字符设备时,就是将这个对象注册到内核中,通过创建一个文件(设备节点)绑定对象的cdev,当我们对这个文件进行读写操作时,就可以通过虚拟文件系统,在内核中找到这个对象及其操作接口,从而控制设备。C 语言中没有面向对象语言的继承的语法,但是我们可以通过结构体的包含来实现继承,这种抽象提取了设备的共性,为上层提供了统一接口,使得管理和操作设备变得很容易。在硬件层,我们可以通过查看硬件的原理图、芯片的数据手册,确定底层需要配置的寄存器,这类似于裸机开发。将对底层寄存器的配置,读写操作放在文件操作接口里面,也就是实现 file_operations 结构体。其次在驱动层,我们将文件操作接口注册到内核,内核通过内部散列表来登记记录主次设备号。在文件系统层,新建一个文件绑定该文件操作接口,应用程序通过操作指定文件的文件操作接口来设置底层寄存器。3.相关概念及数据结构在 linux 中,我们使用设备编号来表示设备,主设备号区分设备类别,次设备号标识具体的设备。字符设备的cdev 结构体记录了设备号,在使用设备时,我们通常会打开设备节点,通过设备节点的 inode 结构体、 file 结构体最终找到 file_operations 结构体,并从file_operations 结构体中得到操作设备的具体方法。3.1.设备号Linux对于设备的访问是通过文件系统的名称进行的,这些名称被称为特殊文件、设备文件,或者简单称为文件系统树的节点, Linux 根目录下有/dev 这个文件夹,专门用来存放设备中的驱动程序。通过执行命令:ls -l /dev,可以查看/dev目录下每个文件的详细信息,每一行的第一个字符表示设备的类型,'c'用来标识字符设备,'b'用来标识块设备。一般来说,主设备号指向设备的驱动程序,次设备号指向某个具体的设备。如上图,I2C-0,I2C-1 属于不同设备但是共用一套驱动程序。在内核中,dev_t 用来表示设备编号,dev_t 是一个 32 位的数,其中,高 12 位表示主设备号,低 20 位表示次设备号。理论上主设备号取值范围:0~2^12-1,次设备号 0~2^20-1。实际上在内核源码中 __register_chrdev_region() 函数中,major 被限定在0~CHRDEV_MAJOR_MAX, CHRDEV_MAJOR_MAX 是一个宏,值是 512。宏定义 MAJOR 和 MINOR,可以根据设备的设备号来获取设备的主设备号和次设备号。宏定义MKDEV,用于将主设备号和次设备号合成一个设备号,主设备号可以通过查阅内核源码的 Documentation/devices.txt 文件,而次设备号通常是从编号 0 开始。内核通过一个散列表 (哈希表) 来记录设备编号。哈希表由数组和链表组成,吸收数组查找快,链表增删效率高,容易拓展等优点。以主设备号为 cdev_map 编号,使用哈希函数 f(major)=major%255 来计算组数下标 (使用哈希函数是为了链表节点尽量平均分布在各个数组元素中,提高查询效率);主设备号冲突, 则以次设备号为比较值来排序链表节点。内核用 struct cdev 结构体来描述一个字符设备,并通过 struct kobj_map 类型的散列表cdev_map 来管理当前系统中的所有字符设备。3.2.设备节点设备节点(设备文件): Linux 中设备节点是通过“mknod”命令来创建的。一个设备节点其实就是一个文件,Linux 中称为设备文件。设备节点被创建在/dev下,是连接内核与用户层的枢纽,就是设备是接到对应哪种接口的哪个 ID 上。设备节点是Linux内核对设备的抽象,一个设备节点就是一个文件。应用程序通过一组标准化的调用执行访问设备,这些调用独立于任何特定的驱动程序。而驱动程序负责将这些标准调用映射到实际硬件的特有操作。3.3.数据结构在驱动开发过程中,不可避免要涉及到三个重要的的内核数据结构分别包括文件操作方式(file_operations),文件描述结构体(struct file)以及inode 结构体。file_operation 就是把系统调用和驱动程序关联起来的关键数据结构。内核中用 file 结构体来表示每个打开的文件,每打开一个文件,内核会创建一个结构体,并将对该文件上的操作函数传递给该结构体的成员变量f_op,当文件所有实例被关闭后,内核会释放这个结构体。在file 结构体中,我们需要关心的数据成员有f_op和private_data:const struct file_operations *f_op:存放与文件操作相关的一系列函数指针,如 open、 read、 wirte 等函数。void *private_data:该指针变量只会用于设备驱动程序中,内核并不会对该成员进行操作。因此,在驱动程序中,通常用于指向描述设备的结构体。inode 结构体是 Linux 管理文件系统的最基本单位,也是文件系统连接任何子目录、文件的桥梁。内核使用 inode 结构体在内核内部表示一个文件。因此,它与表示一个已经打开的文件描述符的结构体 (即 file 文件结构) 是不同的,我们可以使用多个 file 文件结构表示同一个文件的多个文件描述符,但此时,所有的这些 file 文件结构全部都必须只能指向一个 inode 结构体。在inode 结构体中,我们需要关心的数据成员有i_rdev和i_cdev:dev_t i_rdev:表示设备文件的结点,这个域实际上包含了设备号。struct cdev *i_cdev:struct cdev 是内核的一个内部结构,它是用来表示字符设备的,当 inode 结点指向一个字符设备文件时,此域为一个指向 inode 结构的指针。
Linux内核学习驱动开发嵌入式 阅读全文»
广告位B招租:2
 2021-05-23T20:27:54.231148    |      内核学习    |     AilsonJack    |     暂无评论    |     83 views
1.内核模块传参内核模块作为一个可拓展的动态模块,为 Linux 内核提供了灵活性,但是有时我们需要根据不同的应用场景给内核模块传递不同的参数,例如在程序中开启调试模式、设置详细输出模式以及制定与具体模块相关的选项,为了满足这种需求,内核允许对内核模块指定参数,而这些参数可以在装载内核模块时改变。Linux内核提供一个宏module_param来实现模块的参数传递,module_param宏定义在include/linux/moduleparam.h文件中。module_param宏的定义形式如下:#define module_param(name, type, perm)module_param需要三个参数:name:变量的名称;type:变量的类型,目前内核支持的类型有 byte, short, ushort, int, uint, long, ulong, charp, bool,invbool。其中 charp 表示的是字符指针, bool 是布尔类型,其值只能为 0 或者是 1; invbool 是反布尔类型,其值也是只能取 0 或者是 1,但是 true 值表示 0, false 表示 1。变量是 char 类型时,传参只能是 byte,char * 时只能是 charp;perm:sysfs入口项的访问许可掩码,可选的值:S_IRUSR,S_IWUSR,S_IRGRP,S_IWGRP,S_IROTH,S_IXOTH,S_IRUGO,S_IWUGO(这些值可以通过或的方式进行组合,比如S_IRUSR | S_IWUSR表示用户拥有读写权限)。对于这些宏的定义可以参考文件:include/linux/stat.h。上述文件权限唯独没有关于可执行权限的设置,请注意,该文件不允许它具有可执行权限。如果强行给该参数赋予表示可执行权限的参数值 S_IXUGO,那么最终生成的内核模块在加载时会提示错误。这里列举一个简单的内核模块参数传递的例子,module_param.c文件内容:Makefile文件内容:编译module_param内核模块,将生成的module_param.ko文件通过nfs或者scp拷贝到开发板,然后在开发板中执行命令:sudo insmod module_param.ko int_type=12 bool_type=1 char_type=201 str_type="hello!"我们定义的四个模块参数,会在'/sys/module/模块名/parameters'下存在以模块参数为名的文件。由于 int_type 和 char_type 的权限是 0,所以我们没有权限查看该参数。2.符号共享符号共享是指内核模块能够使用其他内核模块导出的符号,或者内核模块将自己模块内的符号导出给其他内核模块使用。这里的符号指的是内核模块中导出的函数或者变量,在加载模块时被记录在公共内核符号表中,以供其他模块调用。这个机制,允许我们使用分层的思想解决一些复杂的模块设计。我们在编写一个驱动的时候,可以把驱动按照功能分成几个内核模块,借助符号共享去实现模块与模块之间的接口调用,变量共享。通常情况下我们无需导出任何符号,但是如果其他模块想要从我们这个模块中获取某些符号的时候,就可以考虑导出符号为其提供服务,这被称为模块层叠技术。例如 msdos 文件系统依赖于由 fat 模块导出的符号;USB 输入设备模块层叠在 usbcore 和 input 模块之上。也就是我们可以将模块分为多个层,通过简化每一层来实现复杂的项目。如果一个模块需要向其他模块导出符号,则应该使用下面的宏:符号必须在模块文件的全局部分导出,不能在函数中使用,EXPORT_SYMBOL_GPL使得导出的模块只能被 GPL 许可的模块使用。这里使用内核模块传参小节的module_param.c文件为基础进行修改,作为一个导出内核模块参数的内核模块,module_param.c文件的内容如下:module_param.h文件内容:symbol_share.c文件内容:Makefile文件内容:编译module_param内核模块和symbol_share内核模块,将生成的module_param.ko和symbol_share.ko文件通过nfs或者scp拷贝到开发板,然后在开发板中执行命令:注意,要先加载module_param内核模块,再加载symbol_share内核模块,因为symbol_share内核模块会依赖module_param内核模块导出的符号,如果先加载symbol_share内核模块,symbol_share内核模块将会加载失败。查看module_param内核模块导出的符号:
Linux内核学习驱动开发嵌入式 阅读全文»
 2021-05-09T16:28:09.912354    |      内核学习    |     AilsonJack    |     暂无评论    |     169 views
1.内核模块的概念因为Linux 操作系统采用了宏内核结构,宏内核的优点是执行效率非常高,但缺点也是十分明显的,一旦我们想要修改、增加内核某个功能时(如增加设备驱动程序)都需要重新编译一遍内核。为了解决这一缺点,Linux 中引入了内核模块这一机制。内核模块就是实现了某个功能的一段内核代码,在内核运行过程,可以加载这部分代码到内核中,从而动态地增加了内核的功能。基于这种特性,我们进行设备驱动开发时,以内核模块的形式编写设备驱动,只需要编译相关的驱动代码即可,无需对整个内核进行编译。内核模块的引入不仅提高了系统的灵活性,对于开发人员来说更是提供了极大的方便。内核模块定义:内核模块全称 Loadable Kernel Module(LKM),是一种在内核运行时加载一组目标代码来实现某个特定功能的机制。内核模块特点:(1).模块本身不被编译入内核映像,这控制了内核的大小;(2).模块一旦被加载,它就和内核中的其它部分完全一样。我们编写的内核模块,经过编译,最终形成以.ko为后缀的文件。ko 文件在数据组织形式上是 ELF(Excutable And Linking Format) 格式,是一种普通的可重定位目标文件。2.编写Hello内核模块对于程序入门学习来说,Hello World程序是经典的例子,这里我们也实现一个简单的Hello内核模块用于了解内核模块编程的基本框架。hello_module.c文件的内容如下所示:2.1.Hello内核模块代码框架分析Linux 内核模块的代码框架通常由下面几个部分组成:(1).模块加载函数 (必须):当通过 insmod 或 modprobe 命令加载内核模块时,模块的加载函数就会自动被内核执行,完成本模块相关的初始化工作。(2).模块卸载函数 (必须):当执行 rmmod 命令卸载模块时,模块卸载函数就会自动被内核自动执行,完成相关清理工作。(3).模块许可证声明 (必须):许可证声明描述内核模块的许可权限,如果模块不声明,模块被加载时,将会有内核被污染的警告。(4).模块参数:模块参数是模块被加载时,可以传值给模块中的参数。(5).模块导出符号:模块可以导出准备好的变量或函数作为符号,以便其他内核模块调用。(6).模块的其他相关信息:可以声明模块作者等信息。2.2.内核模块头文件Hello内核模块中,使用3个头文件,下面说说这3个头文件具体提供的信息:(1).#include <linux/module.h>:包含内核模块信息声明的相关函数;(2).#include <linux/init.h>:包含了 module_init() 和 module_exit() 函数的声明;(3).#include <linux/kernel.h>: 包含内核提供的各种函数,如 printk。2.3.内核模块加载/卸载函数module_init():声明内核模块加载函数,加载内核模块的时候会调用声明的内核模块加载函数,模块加载成功,会在/sys/module下新建一个以模块名为名的目录。module_exit():声明内核模块卸载函数,卸载内核模块的时候会调用声明的内核模块卸载函数。__init 用于修饰函数, __initdata 用于修饰变量。带有 __init 的修饰符,表示将该函数放到可执行文件的 __init 节区中,该节区的内容只能用于模块的初始化阶段,初始化阶段执行完毕之后,这部分的内容就会被释放掉,真可谓是“针尖也要削点铁”。__exit 用于修饰函数,__exitdata 用于修饰变量。带有__exit的修饰符,表示将该函数放到可执行文件的__exit节区,当执行完模块卸载阶段之后,就会自动释放该区域的空间。注意:hello_module_init()函数的返回值是int,hello_module_exit()的返回值是void,并且这两个函数都使用static进行修饰,表示函数只能在本文件进行调用,不能被其他文件调用。2.4.内核打印函数-printkprintk函数的打印等级:printk函数可以指定打印等级,当不指定打印等级的时候,会使用默认的打印等级。查看当前系统 printk 打印等级: cat /proc/sys/kernel/printk,从左到右依次对应控制台日志级别、默认消息日志级别、最小的控制台日志级别、默认控制台日志级别。控制台日志级别:优先级高于该值得消息将被打印到到控制台;默认消息日志级别:将用该优先级来打印没有指定优先级的消息;最小的控制台日志级别:控制台日志级别可被设置的最小值(最高优先级);默认控制台日志级别:控制台日志级别的缺省值。以上的数值设置,数值越小,优先级越高。假设你想让hello_module_init()或者hello_module_exit()函数中,没有指定打印等级的printk的内容输出到控制台,那么你可以将"默认消息日志级别"设置为小于4,可以设置为3(只需要数值小于控制台日志级别即可),执行的命令如下:sudo sh -c "echo '4 3 1 7' > /proc/sys/kernel/printk"然后执行加载或者卸载模块,就可以看到未指定打印等级的消息输出到控制台了。查看内核所有打印信息: dmesg,注意内核 log 缓冲区大小有限制,缓冲区数据可能被覆盖掉。3.内核模块的makefile对于内核模块而言,它是属于内核的一段代码,只不过它并不在内核源码中。为此,我们在编译时需要到内核源码目录下进行编译。编译内核模块使用的 Makefile 文件,和我们前面编译 C 代码使用的 Makefile 大致相同,这得益于编译 Linux 内核所采用的 Kbuild 系统,因此在编译内核模块时,我们也需要指定环境变量 ARCH 和CROSS_COMPILE 的值。编译Hello内核模块使用的Makefile文件内容如下:在内核模块的目录中,执行make命令编译内核模块,生成hello_module.ko文件,将hello_module.ko文件通过nfs或者scp拷贝到开发板,即可加载该内核模块。4.内核模块常用命令4.1.lsmodlsmod 列出当前内核中的所有模块,格式化显示在终端,其原理就是将/proc/module 中的信息调整一下格式输出。 lsmod 输出列表有一列 Used by,它表明此模块正在被其他模块使用,显示了模块之间的依赖关系。4.2.insmod如果要将一个模块加载到内核中, insmod 是最简单的办法, insmod+模块完整路径就能达到目的,前提是你的模块不依赖其他模块,还要注意需要 sudo 权限。如果你不确定是否使用到其他模块的符号,你也可以尝试modprobe,后面会有它的详细用法。4.3.rmmodrmod 工具仅仅是将内核中运行的模块删除,只需要传给它路径就能实现。rmmod 不会卸载一个模块所依赖的模块,需要依次卸载,当然用 modprobe -r 可以一键卸载。4.4.modprobemodprobe 和 insmod 具备同样的功能,同样可以将模块加载到内核中,除此以外 modprobe 还能检查模块之间的依赖关系,并且按照顺序加载这些依赖,可以理解为按照顺序多次执行 insmod。4.5.depmodmodprobe 是怎么知道一个给定模块所依赖的其他的模块呢?在这个过程中, depend 起到了决定性作用,当执行 modprobe 时,它会在模块的安装目录下搜索 module.dep 文件,这是 depmod 创建的模块依赖关系的文件。4.6.modinfomodinfo 用来显示内核模块一些信息。比如:modinfo hello_module.ko5.系统自动加载内核模块我们自己编写了一个模块,或者说怎样让它在板子开机自动加载呢?这里就需要用到上述的 depmod 和 modprobe 工具了。首先需要将我们想要自动加载的模块统一放到”/lib/modules/内核版本”目录下,内核版本使用 'uname -r'查询;其次使用 depmod 建立模块之间的依赖关系,命令’ depmod -a’;这个时候我们就可以在 modules.dep 中看到模块依赖关系。最后在/etc/modules 加上我们自己的模块,注意在该配置文件中,模块不写成.ko 形式代表该模块与内核紧耦合,有些是系统必须要跟内核紧耦合,比如 mm 子系统,一般写成.ko 形式比较好,如果出现错误不会导致内核出现 panic 错误,如果集成到内核,出错了就会出现panic。
Linux内核学习驱动开发嵌入式 阅读全文»
 2021-01-08T23:39:49.486594    |      嵌入式学习    |     AilsonJack    |     暂无评论    |     134 views
1.Makefile简介Makefile是和make工具一起配合使用的,用于组织管理项目源代码的编译和链接。make工具用于找出修改过的文件,根据依赖关系,找出受影响的相关文件,最后按照规则单独编译这些文件。Makefile文件则记录依赖关系和编译规则。Makefile的本质:无论多么复杂的语法,都是为了更好地解决项目文件之间的依赖关系。2.Makefile规则介绍Makefile的一个规则由目标、依赖、命令组成,其语法结构如下所示:目标:依赖的文件或者是其他目标<tab>命令1<tab>命令2<tab>...一个规则可以有多个命令行,每一条命令占一行。注意:每一个命令行必须以[Tab]字符开始,[Tab]字符告诉 make 此行是一个命令行。make 按照命令完成相应的动作。这也是书写 Makefile 中容易产生,而且比较隐蔽的错误。3.Makefile伪目标伪目标不代表一个真正的文件名,在执行 make 时可以指定这个目标来执行其所在规则定义的命令,有时也可以将一个伪目标称为标签。使用伪目标有两点原因:(1).避免在我们的 Makefile 中定义的只执行命令的目标(此目标的目的是为了执行一系列命令,而不需要创建这个目标)和工作目录下的实际文件出现名字冲突。(2).提高执行 make 时的效率,特别是对于一个大型的工程来说,编译的效率也许你同样关心。将一个目标声明为伪目标的方法是将它作为特殊目标".PHONY"的依赖。在书写伪目标规则时,首先需要声明目标是一个伪目标,之后才是伪目标的规则定义。目标"clean"的书写格式应该如下:4.Makefile的变量在 Makefile 中,变量是一个名字(像是 C 语言中的宏),代表一个文本字符串。在 Makefile 的目标、依赖、命令中引用变量的地方,变量会被它的值所取代。变量名是不包括":"、"#"、"="、前置空白和尾空白的任何字符串。变量名最好使用字母、数字和下划线进行定义,对于其他的特殊符号不建议使用到变量名的定义中。变量名是大小写敏感的。变量"foo"、"Foo"和"FOO"指的是三个不同的变量。变量的引用方式是:"$(VARIABLE_NAME)"或者"${VARIABLE_NAME}"来引用一个变量的定义。Makefile的变量包含系统环境变量,自定义变量和自动化变量。4.1.系统环境变量使用系统环境变量需要注意以下几点:(1).在 Makefile 中对一个变量的定义或者以 make 命令行形式对一个变量的定义,都将覆盖同名的系统环境变量(注意:它并不改变系统环境变量定义,被修改的环境变量只在 make 执行过程有效)。而 make 使用"-e"参数时,Makefile 和命令行定义的变量不会覆盖同名的环境变量,make 将使用系统环境变量中这些变量的定义值。(2).make的递归调用中,所有的系统环境变量会被传递给下一级make。默认情况下,只有系统环境变量和通过命令行方式定义的变量才会被传递给子make进程。在Makefile中定义的普通变量需要传递给子make时需要使用"export"指示符来对它声明。4.2.自定义变量在Makefile中自定义变量比较简单,下面简单的区分下各个符号的含义:(1).延迟赋值:"=";使用"="号定义变量时,如果定义的变量存在对其他变量的引用,那么定义的变量并不会立即展开对其他变量的引用,而是在真正使用到该变量时才进行展开。例如下述例子中,只有在echo "$(B)"时才会去展开$(A),虽然在定义变量B之前A的值为123,但是在执行echo "$(B)"时,A的最终值为456,因此会输出456。(2).立即赋值:":=";立即赋值是相对于延迟赋值的,使用":="号定义变量时,如果定义的变量存在对其他变量的引用,那么定义的变量会立即展开对其他变量的引用。可以结合下面的例子进行理解。(3).为空赋值(条件赋值):"?=";使用"?="号定义变量时,只有定义的变量在之前没有赋值的情况下才会对这个变量进行赋值。可以结合下面的例子进行理解。(4).追加赋值:"+=";使用"+="符号,可以对变量的值进行追加。可以结合下面的例子进行理解。例子:4.3.自动化变量Makefile中常用的自动化变量如下描述:$<:第一个依赖文件;$^:全部的依赖文件;$@:目标;5.Makefile条件分支条件分支语法如下:例子:6.Makefile的常用函数Makefile还是提供了比较多的函数供我们使用,这里介绍下常用的几个函数。6.1.patsubst$(patsubst PATTERN,REPLACEMENT,TEXT)函数名称:模式替换函数 - patsubst。函数功能:搜索"TEXT"中以空格分开的单词,将符合模式"PATTERN"替换为"REPLACEMENT"。参数"PATTERN"中可以使用模式通配符"%"来代表一个单词中的若干字符。如果参数"REPLACEMENT"中也包含一个"%",那么"REPLACEMENT"中的"%"将是"PATTERN"中的那个"%"所代表的字符串。在"PATTERN"和"REPLACEMENT"中,只有第一个"%"被作为模式字符来处理,之后出现的不再作模式字符(作为一个字符)。在参数中如果需要将第一个出现的"%"作为字符本身而不作为模式字符时,可使用反斜杠"\"进行转义处理。返回值:替换后的新字符串。 函数说明:参数"TEXT"单词之间的多个空格在处理时被合并为一个空格,并忽略前导和结尾空格。如果感觉上述的文字说明有点不太好理解,那么看看下面的示例吧,之后再结合示例看上面的文字说明应该就比较好理解了,示例如下:上述示例的运行结果:把字串"x.c.c bar.c"中以.c 结尾的单词替换成以.o 结尾的字符。函数的返回结果是"x.c.o bar.o"。6.2.notdir$(notdir NAMES…)函数名称:取文件名函数 - notdir。函数功能:从文件名序列"NAMES…"中取出非目录部分。目录部分是指最后一个斜线("/")(包括斜线)之前的部分。删除所有文件名中的目录部分,只保留非目录部分。返回值:文件名序列"NAMES…"中每一个文件的非目录部分。函数说明:如果"NAMES…"中存在不包含斜线的文件名,则不改变这个文件名。以反斜线结尾的文件名,是用空串代替,因此当"NAMES…"中存在多个这样的文件名时,返回结果中分割各个文件名的空格数目将不确定!这是此函数的一个缺陷。示例:6.3.wildcard$(wildcard PATTERN)函数名称:获取匹配模式文件名函数 - wildcard。函数功能:列出当前目录下所有符合模式"PATTERN"格式的文件名。返回值:空格分割的、存在当前目录下的所有符合模式"PATTERN"的文件名。函数说明:"PATTERN"使用shell可识别的通配符,包括"?"(单字符)、"*"(多字符)等。示例:6.4.foreach$(foreach VAR,LIST,TEXT)函数"foreach"不同于其它函数。它是一个循环函数。类似于 Linux shell 中的for 语句。函数名称:循环函数 - foreach。函数功能:这个函数的工作过程是这样的:如果需要(存在变量或者函数的引用),首先展开变量"VAR"和"LIST"的引用;而表达式"TEXT"中的变量引用不展开。执行时把"LIST"中使用空格分割的单词依次取出赋值给变量"VAR",然后执行"TEXT"表达式。重复直到"LIST"的最后一个单词(为空时结束)。"TEXT"中的变量或者函数引用在执行时才被展开,因此如果在"TEXT"中存在对"VAR"的引用,那么"VAR"的值在每一次展开式将会到的不同的值。返回值:空格分割的多次表达式"TEXT"的计算的结果。备注:函数中参数"VAR"是一个局部的临时变量,它只在"foreach"函数的上下文中有效,它的定义不会影响其它部分定义的同名"VAR"变量的值。示例:示例分析:例子中,"TEXT"的表达式为"$(wildcard $(dir)/*)"。表达式第一次执行时将展开为"$(wildcard a/*)";第二次执行时将展开为"$(wildcard b/*)";第三次展开为"$(wildcard c/*)"; ...;以此类推。7.其他默认规则:.o文件默认使用对应的.c文件来进行编译。
嵌入式LinuxMakefile 阅读全文»
广告位B招租:3
 2019-09-05T22:06:17.969000    |      Linux问题集合    |     AilsonJack    |     暂无评论    |     1537 views
开发环境:Win7 64位开发板:LPC55S69交叉编译工具:2018-q41、问题复现最近在开发LPC55S69的SDK,由于这个芯片采用的是Cortex-M33内核,因此所用到的交叉编译器也是当前比较新的gcc-arm-none-eabi-8-2018-q4-major-win32。在开发过程中,编译自己的SDK时会出现如下问题:ld.exe: warning: xxxxdebugobjtem has a corrupt section with a size(a0dba) larger than the file sizeld.exe: error: xxxxdebugobjtem : ELF section name out of range但是我在编译NXP提供的官方demo时却没有这个问题。最开始出现这个问题的时候,我检查自己的SDK配置是否有问题,始终没有找到问题所在。后来上网搜索相关问题也没有找到这类问题的说明更不要谈解决方法了,再后来就是通过其他搜索引擎查找问题,嘿,几经查找,还终于让我找到了如何解决这个问题的方法,当然了,目前这个问题也完美的解决了。2、问题产生原因好了,来谈谈问题的产生吧:其实这个问题的出现是交叉编译器gcc-arm-none-eabi-8-2018-q4-major-win32的bug,而且这个bug只有在同时使用-flto和-g/-g1/-g3编译条件时才会出现上述问题,并且这个问题也只有在Windows平台才会出现。使用gcc-arm-none-eabi-8-2018-q4-major-win32无论是编译LPC55S69或者是其它ARM平台的代码都会出现这个问题。3、解决办法方法一:由于问题是由gcc-arm-none-eabi-8-2018-q4-major-win32这个版本的交叉编译器产生的,那么解决方法自然是更换其它版本的交叉编译器了,或者使用版本低一点的交叉编译器,或者使用版本高一点的交叉编译器都可以解决这个问题。我这里使用当前(2019.09.05)最新的交叉编译器gcc-arm-none-eabi-8-2019-q3-update-win32,然后编译我的SDK,嘿,编译成功,perfect!方法二:当然了,如果不想更换交叉编译器,这里也可以根据自己的实际情况,去掉-flto或者-g/-g1/-g3编译选项,这样也算是一种解决问题的方法原来编译器也是信不过的,遇到问题还是要好好思考,既考虑自己是否有问题,也要考虑工具是否有问题。好了,问题完美解决,如果觉得这篇文章解决了各位的问题,点个赞或者关注下博主呀,哈哈!这里附上交叉编译器针对这个bug的提交与说明,大家可以了解下(当然了文章是全英文):https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89183https://bugs.launchpad.net/gcc-arm-embedded/+bug/1814397
Linux问题集合gcc嵌入式 阅读全文»
 2018-11-24T09:35:38.438000    |      Linux问题集合    |     AilsonJack    |     暂无评论    |     770 views
最近在使用一个刚装好的Linux操作系统时,在普通用户下使用sudo想重启一下samba服务器,但是系统却告知操作不了,貌似是当前的普通用户没有sudo的执行权限,具体错误信息为:AilsonJack is not in the sudoers file.  This incident will be reported.上述问题出现的原因是当前用户没有加入到sudo的配置文件里,解决方法如下:1、切换到root用户,运行visudo命令;2、找到root ALL=(ALL) ALL,在下面添加一行xxx ALL=(ALL) ALL,其中xxx是要加入的用户名称。至此,对于在普通用户下不能执行sudo命令的问题已经解决了,有什么问题,欢迎留言。
Linux问题集合 阅读全文»
 2018-11-15T19:14:34.396000    |      汇编    |     AilsonJack    |     暂无评论    |     821 views
BIOS int 13H中断也叫直接磁盘服务(Direct Disk Service),该中断的各个功能号及对应的功能描述如下:功能号功能描述功能号功能描述00H磁盘系统复位01H读取磁盘系统状态02H读扇区03H写扇区04H检验扇区05H格式化磁道06H格式化坏磁道07H格式化驱动器08H读取驱动器参数09H初始化硬盘参数0AH读长扇区0BH写长扇区0CH查寻0DH硬盘系统复位0EH读扇区缓冲区0FH写扇区缓冲区10H读取驱动器状态11H校准驱动器12H控制器RAM诊断13H控制器驱动诊断14H控制器内部诊断15H读取磁盘类型16H读取磁盘变化状态17H设置磁盘类型18H设置格式化媒体类型19H设置磁盘类型1AH格式化ESDI驱动器1、功能 00H功能描述:磁盘系统复位入口参数:AH=00HDL=驱动器,00H~7FH:软盘;80H~0FFH:硬盘出口参数:CF=0——操作成功,AH=00H,否则,AH=状态代码,参见功能号01H中的说明2、功能 01H功能描述:读取磁盘系统状态入口参数:AH=01HDL=驱动器,00H~7FH:软盘;80H~0FFH:硬盘出口参数:AH=00H,AL=状态代码,其定义如下:00H — 无错 01H — 非法命令02H — 地址目标未发现 03H — 磁盘写保护(软盘)04H — 扇区未发现 05H — 复位失败(硬盘)06H — 软盘取出(软盘) 07H — 错误的参数表(硬盘)08H — DMA越界(软盘) 09H — DMA超过64K界限0AH — 错误的扇区标志(硬盘) 0BH — 错误的磁道标志(硬盘)0CH — 介质类型未发现(软盘) 0DH — 格式化时非法扇区号(硬盘)0EH — 控制数据地址目标被发现(硬盘) 0FH — DMA仲裁越界(硬盘)10H — 不正确的CRC或ECC编码 11H — ECC校正数据错(硬盘) CRC:Cyclic Redundancy Check code ECC:Error Checking & Correcting code20H — 控制器失败 40H — 查找失败80H — 磁盘超时(未响应) AAH — 驱动器未准备好(硬盘)BBH — 未定义的错误(硬盘) CCH — 写错误(硬盘)E0H — 状态寄存器错(硬盘) FFH — 检测操作失败(硬盘)3、功能 02H功能描述:读扇区入口参数:AH=02HAL=扇区数CH=柱面CL=扇区DH=磁头DL=驱动器,00H~7FH:软盘;80H~0FFH:硬盘ES:BX=缓冲区的地址出口参数:CF=0——操作成功,AH=00H,AL=传输的扇区数,否则,AH=状态代码,参见功能号01H中的说明4、功能 03H功能描述:写扇区入口参数:AH=03HAL=扇区数CH=柱面CL=扇区DH=磁头DL=驱动器,00H~7FH:软盘;80H~0FFH:硬盘ES:BX=缓冲区的地址出口参数:CF=0——操作成功,AH=00H,AL=传输的扇区数,否则,AH=状态代码,参见功能号01H中的说明5、功能 04H功能描述:检验扇区入口参数:AH=04HAL=扇区数CH=柱面CL=扇区DH=磁头DL=驱动器,00H~7FH:软盘;80H~0FFH:硬盘ES:BX=缓冲区的地址出口参数:CF=0——操作成功,AH=00H,AL=被检验的扇区数,否则,AH=状态代码,参见功能号01H中的说明。6、功能 05H功能描述:格式化磁道入口参数:AH=05HAL=交替(Interleave)CH=柱面DH=磁头DL=驱动器,00H~7FH:软盘;80H~0FFH:硬盘ES:BX=地址域列表的地址出口参数:CF=0——操作成功,AH=00H,否则,AH=状态代码,参见功能号01H中的说明。7、功能 06H功能描述:格式化坏磁道入口参数:AH=06HAL=交替CH=柱面DH=磁头DL=80H~0FFH:硬盘ES:BX=地址域列表的地址出口参数:CF=0——操作成功,AH=00H,否则,AH=状态代码,参见功能号01H中的说明8、功能 07H功能描述:格式化驱动器入口参数:AH=07HAL=交替CH=柱面DL=80H~0FFH:硬盘出口参数:CF=0——操作成功,AH=00H,否则,AH=状态代码,参见功能号01H中的说明9、功能 08H功能描述:读取驱动器参数入口参数:AH=08HDL=驱动器,00H~7FH:软盘;80H~0FFH:硬盘出口参数:CF=1——操作失败,AH=状态代码,参见功能号01H中的说明,否则,BL=01H — 360K=02H — 1.2M=03H — 720K=04H — 1.44MCH=柱面数的低8位CL的位7-6=柱面数的该2位CL的位5-0=扇区数DH=磁头数DL=驱动器数ES:DI=磁盘驱动器参数表地址10、功能 09H功能描述:初始化硬盘参数入口参数:AH=09HDL=80H~0FFH:硬盘(还有有关参数表问题,在此从略)出口参数:CF=0——操作成功,AH=00H,否则,AH=状态代码,参见功能号01H中的说明11、功能 0AH功能描述:读长扇区,每个扇区随带四个字节的ECC编码入口参数:AH=0AHAL=扇区数CH=柱面CL=扇区DH=磁头DL=80H~0FFH:硬盘ES:BX=缓冲区的地址出口参数:CF=0——操作成功,AH=00H,AL=传输的扇区数,否则,AH=状态代码,参见功能号01H中的说明12、功能 0BH功能描述:写长扇区,每个扇区随带四个字节的ECC编码入口参数:AH=0BHAL=扇区数CH=柱面CL=扇区DH=磁头DL=80H~0FFH:硬盘ES:BX=缓冲区的地址出口参数:CF=0——操作成功,AH=00H,AL=传输的扇区数,否则,AH=状态代码,参见功能号01H中的说明13、功能 0CH功能描述:查寻入口参数:AH=0CHCH=柱面的低8位CL(7-6位)=柱面的高2位DH=磁头DL=80H~0FFH:硬盘出口参数:CF=0——操作成功,AH=00H,否则,AH=状态代码,参见功能号01H中的说明14、功能 0DH功能描述:硬盘系统复位入口参数:AH=0DHDL=80H~0FFH:硬盘出口参数:CF=0——操作成功,AH=00H,否则,AH=状态代码,参见功能号01H中的说明15、功能 0EH功能描述:读扇区缓冲区入口参数:AH=0EHES:BX=缓冲区的地址出口参数:CF=0——操作成功,否则,AH=状态代码,参见功能号01H中的说明16、功能 0FH功能描述:写扇区缓冲区入口参数:AH=0FHES:BX=缓冲区的地址出口参数:CF=0——操作成功,否则,AH=状态代码,参见功能号01H中的说明17、功能 10H功能描述:读取驱动器状态入口参数:AH=10HDL=80H~0FFH:硬盘出口参数:CF=0——操作成功,AH=00H,否则,AH=状态代码,参见功能号01H中的说明18、功能 11H功能描述:校准驱动器入口参数:AH=11HDL=80H~0FFH:硬盘出口参数:CF=0——操作成功,AH=00H,否则,AH=状态代码,参见功能号01H中的说明19、功能 12H功能描述:控制器RAM诊断入口参数:AH=12H出口参数:CF=0——操作成功,否则,AH=状态代码,参见功能号01H中的说明20、功能 13H功能描述:控制器驱动诊断入口参数:AH=13H出口参数:CF=0——操作成功,否则,AH=状态代码,参见功能号01H中的说明21、功能 14H功能描述:控制器内部诊断入口参数:AH=14H出口参数:CF=0——操作成功,否则,AH=状态代码,参见功能号01H中的说明22、功能 15H功能描述:读取磁盘类型入口参数:AH=15HDL=驱动器,00H~7FH:软盘;80H~0FFH:硬盘出口参数:CF=1——操作失败,AH=状态代码,参见功能号01H中的说明, 否则,AH=00H — 未安装驱动器=01H — 无改变线支持的软盘驱动器=02H — 带有改变线支持的软盘驱动器=03H — 硬盘,CX:DX=512字节的扇区数23、功能 16H功能描述:读取磁盘变化状态入口参数:AH=16HDL=00H~7FH:软盘出口参数:CF=0——磁盘未改变,AH=00H,否则,AH=06H,参见功能号01H中的说明24、功能 17H功能描述:设置磁盘类型入口参数:AH=17HDL=00H~7FH:软盘 AL=00H — 未用=01H — 360K在360K驱动器中=02H — 360K在1.2M驱动器中=03H — 1.2M在1.2M驱动器中=04H — 720K在720K驱动器中出口参数:CF=0——操作成功,AH=00H,否则,AH=状态编码,参见功能号01H中的说明25、功能 18H功能描述:设置格式化媒体类型入口参数:AH=18HCH=柱面数CL=每磁道的扇区数DL=00H~7FH:软盘出口参数:CF=0——操作成功,AH=00H,ES:DI=介质类型参数表地址,否则,AH=状态编码,参见功能号01H中的说明26、功能 19H功能描述:磁头保护,仅在PS/2中有效,在此从略27、功能 1AH功能描述:格式化ESDI驱动器,仅在PS/2中有效,在此从略
汇编中断Linux 阅读全文»
广告位B招租:4
广告位B招租:2
广告位B招租:3
广告位B招租:4
  • 1

  本站信息

目前本站共被浏览 125969 次
目前本站已经运行 2645 天
目前本站共有 145 篇文章
目前本站共有 2 条评论信息
目前本站共有 97 个标签
目前本站共有 0 条留言信息
网站创建时间: 2015年03月01日
最近更新时间: 2022年05月05日
广告位E招租
Copyright © 2015~2021  说好一起走   保留所有权利   |  百度统计  蜀ICP备15004292号