1.字符设备中的源码几个函数分析
2.linux驱动头文件位置的说明
3.Linux驱动(驱动程序开发、驱动框架代码编译和测试)
4.访问注册表出错
5.arduino如何输出一个数组?如位置坐标(x,源码y).不是赋值而是输出。
6.金三银四Linux内核面试题(2022最新版)
字符设备中的源码几个函数分析
1.在内核中, dev_t 类型(在 <linux/types.h>中定义)用来持有设备编号 — 主次部分都包括.其中dev_t 是 位的量, 位用作主编号, 位用作次编号
1 #ifndef _LINUX_TYPES_H
2 #define _LINUX_TYPES_H
3
4 #include <asm/types.h>
5
6 #ifndef __ASSEMBLY__
7 #ifdef __KERNEL__
8
9 #define DECLARE_BITMAP(name,bits) /
unsigned long name[BITS_TO_LONGS(bits)]
#endif
#include <linux/posix_types.h>
#ifdef __KERNEL__
typedef __u __kernel_dev_t;
typedef __kernel_fd_set fd_set;
typedef __kernel_dev_t dev_t; //用来持有设备编号的主次部分
typedef __kernel_ino_t ino_t;
typedef __kernel_mode_t mode_t;
...
2.在 <linux/kdev_t.h>中的一套宏定义. 为获得一个 dev_t 的主或者次编号, 使用:
2.1设备编号的内部表示
MAJOR(dev_t dev);
MINOR(dev_t dev);
2.在有主次编号时, 需要将其转换为一个 dev_t, 可使用:
MKDEV(int major, int minor);
在linux/kdev_t.h中有下了内容
...
4 #define MINORBITS
5 #define MINORMASK ((1U << MINORBITS) - 1)
6
7 #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
8 #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
9 #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))//高为表示主设备号,低位表示次设备号
...
3.分配和释放设备编号register_chrdev_region函数
下面摘自文件fs/char_dev.c内核源代码
/
*** register_chrdev_region() - register a range of device numbers
* @from: the first in the desired range of device numbers; must include
* the major number.
* @count: the number of consecutive device numbers required
* @name: the name of the device or driver.
*
* Return value is zero on success, a negative error code on failure.
*/
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
struct char_device_struct *cd;
dev_t to = from + count; //计算分配号范围中的最大值+=
dev_t n, next;
for (n = from; n < to; n = next) { /*每次申请个设备号*/
next = MKDEV(MAJOR(n)+1, 0);/*主设备号加一得到的设备号,次设备号为0*/
if (next > to)
next = to;
cd = __register_chrdev_region(MAJOR(n),源码 MINOR(n),
next - n, name);
if (IS_ERR(cd))
goto fail;
}
return 0;
fail:/*当一次分配失败的时候,释放所有已经分配到地设备号*/
to = n;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
}
return PTR_ERR(cd);
}
这里, from是要分配的起始设备编号. from 的次编号部分常常是 0, 但是没有要求是那个效果. count是你请求的连续设备编号的总数. 注意, 如果count 太大, 要求的范围可能溢出到下一个次编号;但是只要要求的编号范围可用, 一切都仍然会正确工作. 最后, name 是应当连接到这个编号范围的设备的名子; 它会出现在 /proc/devices 和 sysfs 中.如同大部分内核函数, 如果分配成功进行, register_chrdev_region 的返回值是 0. 出错的情况下, 返回一个负的错误码, 不能存取请求的区域.
4.下面是char_device_struct结构体的信息
fs/char_dev.c
static struct char_device_struct {
struct char_device_struct *next; // 指向散列冲突链表中的下一个元素的指针
unsigned int major; // 主设备号
unsigned int baseminor; // 起始次设备号
int minorct; // 设备编号的范围大小
const char *name; // 处理该设备编号范围内的设备驱动的名称
struct file_operations *fops; // 没有使用
struct cdev *cdev; /* will die指向字符设备驱动程序描述符的指针*/
} *chrdevs[MAX_PROBE_HASH];
/
** Register a single major with a specified minor range.
*
* If major == 0 this functions will dynamically allocate a major and return
* its number.
*
* If major > 0 this function will attempt to reserve the passed range of
* minors and will return zero on success.
*
* Returns a -ve errno on failure.
*/
/
*** 该函数主要是注册注册注册主设备号和次设备号
* major == 0此函数动态分配主设备号
* major > 0 则是申请分配指定的主设备号
* 返回0表示申请成功,返 回负数说明申请失败
*/
static struct char_device_struct
*__register_chrdev_region(unsigned int major, unsigned int baseminor,
int minorct, const char *name)
{ /*以下处理char_device_struct变量的初始化和注册*/
struct char_device_struct *cd, **cp;
int ret = 0;
int i;
//kzalloc()分配内存并且全部初始化为0,
cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
if (cd == NULL)
//ENOMEM定义在include/asm-generic/error-base.h中,
// #define ENOMEM /* Out of memory */
return ERR_PTR(-ENOMEM);
mutex_lock(&chrdevs_lock);
/* temporary */
if (major == 0) { //下面动态申请主设备号
for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i—) {
//ARRAY_SIZE是定义为ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
//#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
if (chrdevs[i] == NULL)
//chrdevs是内核中已经注册了的设备好设备的一个数组
break;
}
if (i == 0) {
ret = -EBUSY;
goto out;
}
major = i;
ret = major;//这里得到一个位使用的设备号
}
//下面四句是对已经申请到的设备数据结构进行填充
cd->major = major;
cd->baseminor = baseminor;
cd->minorct = minorct;/*申请设备号的个数*/
strlcpy(cd->name, name, sizeof(cd->name));
/*以下部分将char_device_struct变量注册到内核*/
i = major_to_index(major);
for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
if ((*cp)->major > major || //chardevs[i]设备号大于主设备号
((*cp)->major == major &&
(((*cp)->baseminor >= baseminor) || //chardevs[i]主设备号等于主设备号,并且此设备号大于baseminor
((*cp)->baseminor + (*cp)->minorct > baseminor))))
break;
//在字符设备数组中找到现在注册的源码设备
/* Check for overlapping minor ranges. */
if (*cp && (*cp)->major == major) {
int old_min = (*cp)->baseminor;
int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
int new_min = baseminor;
int new_max = baseminor + minorct - 1;
/* New driver overlaps from the left. */
if (new_max >= old_min && new_max <= old_max) {
ret = -EBUSY;
goto out;
}
/* New driver overlaps from the right. */
if (new_min <= old_max && new_min >= old_min) {
ret = -EBUSY;
goto out;
}
}
/*所申请的设备好号能够满足*/
cd->next = *cp;/*按照主设备号从小到大顺序排列*/
*cp = cd;
mutex_unlock(&chrdevs_lock);
return cd;
out:
mutex_unlock(&chrdevs_lock);
kfree(cd);
return ERR_PTR(ret);
}
以上程序大体上分为两个步骤:
1.char_device_struct类型变量的分配以及初始化~行
2.将char_device_struct变量注册到内核,行页到行
1.char_device_struct类型变量的源码表白源码c分配以及初始化
(1)首先,调用 kmalloc 分配一个 char_device_struct 变量cd。源码
检查返回值,源码进行错误处理。源码
(2)将分配的源码char_device_struct变量的内存区清零memset。
(3)获取chrdevs_lock读写锁,源码并且关闭中断,源码禁止内核抢占,源码write_lock_irq。源码
(4)如果传入的源码主设备号major不为0,跳转到第(7)步。
(5)这时,major为0,首先需要分配一个合适的主设备号。
将 i 赋值成 ARRAY_SIZE(chrdevs)-1,其中的 chrdevs 是包含有个char_device_struct *类型的数组,
然后递减 i 的值,直到在chrdevs数组中出现 NULL。当chrdevs数组中不存在空值的时候,
ret = -EBUSY; goto out;
(6)到达这里,就表明主设备号major已经有合法的值了,接着进行char_device_struct变量的初始化。
设置major, baseminor, minorct以及name。
2.将char_device_struct变量注册到内核
(7)将 i 赋值成 major_to_index(major)
将major对取余数,得到可以存放char_device_struct在chrdevs中的索引
(8)进入循环,在chrdevs[i]的链表中找到一个合适位置。
退出循环的条件:
(1)chrdevs[i]为空。
(2)chrdevs[i]的主设备号大于major。
(3)chrdevs[i]的主设备号等于major,但是次设备号大于等于baseminor。
注意:cp = &(*cp)->next,cp是char_device_struct **类型,(*cp)->next是一个char_device_struct
*类型,所以&(*cp)->next,就得到一个char_device_struct **,并且这时候由于是指针,所以
对cp赋值,就相当于对链表中的元素的next字段进行操作。
(9)进行冲突检查,因为退出循环的情况可能造成设备号冲突(产生交集)。
如果*cp不空,并且*cp的major与要申请的major相同,此时,如果(*cp)->baseminor < baseminor + minorct,
就会发生冲突,因为和已经分配了的设备号冲突了。出错就跳转到ret = -EBUSY; goto out;
()到这里,内核可以满足设备号的申请,将cd链接到链表中。
()释放chrdevs_lock读写锁,开中断,开内核抢占。
()返回加入链表的char_device_struct变量cd。
()out出错退出
a.释放chrdevs_lock读写锁,开中断,开内核抢占。
b.释放char_device_struct变量cd,kfree。赤壁指标源码
c.返回错误信息
下面程序出自fs/char_dev.c
动态申请设备号
...
/
*** alloc_chrdev_region() - register a range of char device numbers
* @dev: output parameter for first assigned number
* @baseminor: first of the requested range of minor numbers
* @count: the number of minor numbers required
* @name: the name of the associated device or driver
*
* Allocates a range of char device numbers. The major number will be
* chosen dynamically, and returned (along with the first minor number)
* in @dev. Returns zero or a negative error code.
*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
{
/* dev:
仅仅作为输出参数,成功分配后将保存已分配的第一个设备编号。
baseminor:
被请求的第一个次设备号,通常是0。
count:
所要分配的设备号的个数。
name:
和所分配的设备号范围相对应的设备名称。
b.返回值:
成功返回0,失败返回负的错误编码
*/
struct char_device_struct *cd;
cd = __register_chrdev_region(0, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
*dev = MKDEV(cd->major, cd->baseminor);
return 0;
}
...
linux驱动头文件位置的说明
在开发Linux驱动程序时,理解头文件的位置是至关重要的。不同版本的Linux内核源码中,头文件的位置会有所差异。例如,对于一个名为regs-gpio.h的文件,在较早期版本(如2.6.)中,它位于arch/arm/include/asm目录下;而在较新版本(如2.6.)中,则可能位于arch/arm/mach-s3c/include/mach目录。因此,使用特定内核版本时,务必明确头文件所在的具体位置。 为了进行正确的包含,你需要根据实际使用的Linux内核版本,确定包含路径。例如,对于Linux-2.6.版本,`#include`语句可能需要分别在`linux-2.6./include/linux`、`linux-2.6./arch/arm/include/asm`、`linux-2.6./arch/arm/mach-s3c/include/mach`等目录下查找源文件。不同版本的内核会根据其架构和特性,将各种头文件组织在特定的目录下,以满足不同硬件平台的需求。 包含的头文件主要涉及Linux驱动程序开发中的核心模块。例如:`#include`:提供动态加载和卸载模块的基础功能。
`#include`:包含了文件操作相关的结构定义,如`struct file_operations`等。
`#include`:定义了错误处理相关的宏,使用户程序能够通过`perror`函数输出错误信息。
`#include`:提供了各种数据类型定义,如`dev_t`、`off_t`、`pid_t`等,这些类型在驱动编程中广泛使用。
`#include`:包含了字符设备结构`cdev`及相关操作函数的定义。
`#include`:涉及等待队列、中断处理、定时器等内核核心功能的头文件。
`#include`:与处理器相关的中断处理功能。
`#include`:定义了内核等待队列中的常数,如`TASK_NORMAL`、`TASK_INTERRUPTIBLE`。
`#include`:提供了fifo(先进先出)队列的实现。
`#include`:包含了内核定时器的定义和使用。
`#include`:涉及中断处理机制的头文件。
`#include`:提供了与处理器相关的IO口操作的函数。
`#include`:用于访问硬件设备的IO控制功能。
这些头文件构成了Linux驱动程序开发的基础,它们定义了内核中的各种数据结构、函数原型和常量,是编写驱动程序不可或缺的资源。理解并正确使用这些头文件,能够帮助开发者更高效地开发和调试驱动程序。Linux驱动(驱动程序开发、驱动框架代码编译和测试)
驱动就是OSL开源码对底层硬件设备的操作进行封装,并向上层提供函数接口。
Linux系统将设备分为3类:字符设备、块设备、网络设备。
先看一张图,图中描述了流程,有助了解驱动。
用户态:
内核态:
驱动链表:管理所有设备的驱动,添加或查找, 添加是发生在我们编写完驱动程序,加载到内核。查找是在调用驱动程序,由应用层用户空间去查找使用open函数。驱动插入链表的顺序由设备号检索。
字符设备驱动工作原理:
在Linux的世界里一切皆文件,所有的硬件设备操作到应用层都会被抽象成文件的操作。当应用层要访问硬件设备,它必定要调用到硬件对应的驱动程序。Linux内核有那么多驱动程序,应用怎么才能精确的调用到底层的驱动程序呢?
当open函数打开设备文件时,可以根据设备文件对应的struct inode结构体描述的信息,可以知道接下来要操作的设备类型(字符设备还是块设备),还会分配一个struct file结构体。
根据struct inode结构体里面记录的设备号,可以找到对应的驱动程序。在Linux操作系统中每个字符设备都有一个struct cdev结构体。此结构体描述了字符设备所有信息,其中最重要的一项就是字符设备的操作函数接口。
找到struct cdev结构体后,linux内核就会将struct cdev结构体所在的内存空间首地址记录在struct inode结构体i_cdev成员中,将struct cdev结构体中的记录的函数操作接口地址记录在struct file结构体的f_ops成员中。
任务完成,VFS层会给应用返回一个文件描述符(fd)。这个fd是和struct file结构体对应的。接下来上层应用程序就可以通过fd找到struct file,然后在struct file找到操作字符设备的函数接口file_operation了。
其中,cdev_init和cdev_add在驱动程序的入口函数中就已经被调用,分别完成字符设备与file_operation函数操作接口的绑定,和将字符驱动注册到内核的工作。
驱动程序开发步骤:
Linux 内核就是由各种驱动组成的,内核源码中有大约 %是各种驱动程序的代码。内核中驱动程序种类齐全,可以在同类驱动的基础上进行修改以符合具体单板。
编写驱动程序的难点并不是硬件的具体操作,而是弄清楚现有驱动程序的框架,在这个框架中加入这个硬件。
一般来说,编写一个 linux 设备驱动程序的大致流程如下:
下面以一个简单的字符设备驱动框架代码来进行驱动程序的开发、编译等。
基于驱动框架的代码开发:
上层调用代码
驱动框架代码
驱动开发的重点难点在于读懂框架代码,在里面进行设备的添加和修改。
驱动框架设计流程:
1. 确定主设备号
2. 定义结构体 类型 file_operations
3. 实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构体
4. 实现驱动入口:安装驱动程序时,就会去调用这个入口函数,执行工作:
① 把 file_operations 结构体告诉内核:注册驱动程序register_chrdev.
② 创建类class_create.
③ 创建设备device_create.
5. 实现出口:卸载驱动程序时,就会去调用这个出口函数,执行工作:
① 把 file_operations 结构体从内核注销:unregister_chrdev.
② 销毁类class_create.
③ 销毁设备结点device_destroy.
6. 其他完善:GPL协议、入口加载
驱动模块代码编译和测试:
编译阶段:
驱动模块代码编译(模块的编译需要配置过的内核源码,编译、连接后生成的内核模块后缀为.ko,编译过程首先会到内核源码目录下,恋爱季源码读取顶层的Makefile文件,然后再返回模块源码所在目录。)
将该驱动代码拷贝到 linux-rpi-4..y/drivers/char 目录下 文件中(也可选择设备目录下其它文件)
修改该文件夹下Makefile(驱动代码放到哪个目录,就修改该目录下的Makefile),将上面的代码编译生成模块,文件内容如下图所示:(-y表示编译进内核,-m表示生成驱动模块,CONFIG_表示是根据config生成的),所以只需要将obj-m += pin4drive.o添加到Makefile中即可。
回到linux-rpi-4..y/编译驱动文件
使用指令:ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules进行编译生成驱动模块。
加载内核驱动:
加载内核驱动(相当于通过insmod调用了module_init这个宏,然后将整个结构体加载到驱动链表中)。 加载完成后就可以在dev下面看到名字为pin4的设备驱动(这个和驱动代码里面static char *module_name="pin4"; //模块名这行代码有关),设备号也和代码里面相关。
lsmod查看系统的驱动模块,执行上层代码,赋予权限
查看内核打印的信息,如下图所示:表示驱动调用成功
在装完驱动后可以使用指令:sudo rmmod +驱动名(不需要写ko)将驱动卸载。
驱动调用流程:
上层空间的open去查找dev下的驱动(文件名),文件名背后包含了驱动的主设备号和次设备号。此时用户open触发一个系统调用,系统调用经过vfs(虚拟文件系统),vfs根据文件名背后的设备号去调用sys_open去判断,找到内核中驱动链表的驱动位置,再去调用驱动里面自己的dev_open函数。
为什么生成驱动模块需要在虚拟机上生成?树莓派不行吗?
生成驱动模块需要编译环境(linux源码并且编译,需要下载和系统版本相同的Linux内核源代码)。也可以在树莓派上面编译,但在树莓派里编译,效率会很低,要非常久。
访问注册表出错
访问注册表出错因为每台 电脑的用户 不同 访问 权限不同
只要替换 你这个 S-1-5----- 就可以导入
运行 regedit
你打开注册表找到这个项 是什么 数字
替换上面的数字 即可 导入
程序不知道怎么调,那个MBUS
驱动程序开发的一个重大难点就是不易调试。本文目的就是介绍驱动开发中常用的几种直接和间接的调试手段,它们是:
1、利用printk
2、查看OOP消息
3、利用strace
4、利用内核内置的hacking选项
5、利用ioctl方法
6、利用/proc 文件系统
7、使用kgdb
前两种如下:
一、利用printk
这是驱动开发中最朴实无华,同时也是最常用和有效的手段。scull驱动的main.c第行如下,就是使用printk进行调试的例子,这样的例子相信大家在阅读驱动源码时随处可见。
// printk(KERN_ALERT "wakeup by signal in process %d\n", current-pid);
printk的功能与我们经常在应用程序中使用的printf是一样的,不同之处在于printk可以在打印字符串前面加上内核定义的宏,例如上面例子中的KERN_ALERT(注意:宏与字符串之间没有逗号)。
#define KERN_EMERG "0"
#define KERN_ALERT "1"
#define KERN_CRIT "2"
#define KERN_ERR "3"
#define KERN_WARNING "4"
#define KERN_NOTICE "5"
#define KERN_INFO "6"
#define KERN_DEBUG "7"
#define DEFAULT_CONSOLE_LOGLEVEL 7
这个宏是用来定义需要打印的字符串的级别。值越小,级别越高。内核中有个参数用来控制是否将printk打印的字符串输出到控制台(屏幕或者/sys/log/syslog日志文件)
# cat /proc/sys/kernel/printk
6 4 1 7
第一个6表示级别高于(小于)6的消息才会被输出到控制台,第二个4表示如果调用printk时没有指定消息级别(宏)则消息的级别为4,第三个1表示接受的最高(最小)级别是1,第四个7表示系统启动时第一个6原来的初值是7。
因此,如果你发现在控制台上看不到你程序中某些printk的输出,请使用echo 8 /proc/sys/kernel/printk来解决。
在复杂驱动的开发过程中,为了调试会在源码中加入成百上千的岁月西游源码printk语句。而当调试完毕形成最终产品的时候必然会将这些printk语句删除想想驱动的使用者而不是开发者吧。记住:己所不欲,勿施于人),这个工作量是不小的。最要命的是,如果我们将调试用的printk语句删除后,用户又报告驱动有bug,所以我们又不得不手工将这些上千条的printk语句再重新加上。oh,my god,杀了我吧。所以,我们需要一种能方便地打开和关闭调试信息的手段。哪里能找到这种手段呢?哈哈,远在天边,近在眼前。看看scull驱动或者leds驱动的源代码吧!
#define LEDS_DEBUG
#undef PDEBUG
#ifdef LEDS_DEBUG
#ifdef __KERNEL__
#define PDEBUG(fmt, args…) printk( KERN_EMERG "leds: " fmt, ## args)
#else
#define PDEBUG(fmt, args…) fprintf(stderr, fmt, ## args)
#endif
#else
#define PDEBUG(fmt, args…)
#endif
#undef PDEBUGG
#define PDEBUGG(fmt, args…)
这样一来,在开发驱动的过程中,如果想打印调试消息,我们就可以用PDEBUG("address of i_cdev is %p\n", inode-i_cdev);,如果不想看到该调试消息,就只需要简单的将PDEBUG改为PDEBUGG即可。而当我们调试完毕形成最终产品时,只需要简单地将第1行注释掉即可。
上边那一段代码中的__KERNEL__是内核中定义的宏,当我们编译内核(包括模块)时,它会被定义。当然如果你不明白代码中的…和##是什么意思的话,就请认真查阅一下gcc关于预处理部分的资料吧!如果你实在太懒不愿意去查阅的话,那就充当VC工程师把上面的代码copy到你的代码中去吧。
二、查看OOP消息
OOP意为惊讶。当你的驱动有问题,内核不惊讶才怪:嘿!小子,你干吗乱来!好吧,就让我们来看看内核是如何惊讶的。
根据faulty.c(单击下载)编译出faulty.ko,并 inod faulty.ko。执行echo yang /dev/faulty,结果内核就惊讶了。内核为什么会惊讶呢?因为faulty驱动的write函数执行了(int )0 = 0,向内存0地址写入,这是内核绝对不会容许的。
ssize_t faulty_write (struct file filp, const char __user buf, size_t count,
loff_t pos)
{
(int )0 = 0;
return 0;
}
1 Unable to handle kernel NULL pointer dereference at virtual address
2 pgd = c
3 [] pgd=, pte=, ppte=
4 Internal error: Oops: [#1] PREEMPT
5 Modules linked in: faulty scull
6 CPU: 0 Not tainted (2.6..6 #4)
7 PC is at faulty_write0×/0× [faulty]
8 LR is at vfs_write0xc4/0×
9 pc : [] lr : [] psr: a
sp : cf ip : cf fp : cf
r: c r9 : c r8 :
r7 : r6 : cf r5 : r4 : ce
r3 : cf r2 : r1 : r0 :
Flags: NzCv IRQs on FIQs on Mode SVC_ Segment user
Control: cf Table: DAC:
Process sh (pid: , stack limit = 0xc)
Stack: (0xcf to 0xc)
1f: cf cf ceb8 bfc ce ce
1f: cf cfa4 cf cffc ce
1f: cc0e4 cfa8
1fa0: cbf cfc0
1fc0: c
1fe0: bea c adb
Backtrace:
[] (faulty_write0×0/0× [faulty]) from [] (vfs_write0xc4/0×)
[] (vfs_write0×0/0×) from [] (sys_write0x4c/0×)
r7: r6:cf r5:ce r4:ce
[] (sys_write0×0/0×) from [] (ret_fast_syscall0×0/0x2c)
r8:cc0e4 r7: r6: r5: r4:
Code: e1a0cd edd ecb e3a (e)
1行惊讶的原因,也就是报告出错的原因;
2-4行是OOP信息序号;
5行是出错时内核已加载模块;
6行是发生错误的CPU序号;
7-行是发生错误的位置,以及当时CPU各个寄存器的值,这最有利于我们找出问题所在地;
行是当前进程的名字及进程ID
-行是出错时,栈内的内容
-行是栈回溯信息,可看出直到出错时的函数递进调用关系(确保CONFIG_FRAME_POINTER被定义)
行是出错指令及其附近指令的机器码,出错指令本身在小括号中
反汇编faulty.ko( arm-linux-objdump -D faulty.ko faulty.dis ;cat faulty.dis)可以看到如下的语句如下:
c :
7c: e1a0cd mov ip, sp
: edd stmdb sp!, { fp, ip, lr, pc}
: ecb sub fp, ip, #4 ; 0×4
: e3a mov r0, #0 ; 0×0
8c: e str r0, [r0]
: eda ldmia sp, { fp, sp, pc}
定位出错位置以及获取相关信息的过程:
9 pc : [] lr : [] psr: a
[] (faulty_write0×0/0× [faulty]) from [] (vfs_write0xc4/0×)
[] (vfs_write0×0/0×) from [] (sys_write0x4c/0×)
出错代码是faulty_write函数中的第5条指令((0xbfc-0xbfc)/=5),该函数的首地址是0xbfc,该函数总共6条指令(0×),该函数是被0xceb8的前一条指令调用的(即:函数返回地址是0xceb8。这一点可以从出错时lr的值正好等于0xceb8得到印证)。调用该函数的指令是vfs_write的第条(0xc4/4=)指令。
达到出错处的函数调用流程是:write(用户空间的系统调用)–sys_write–vfs_write–faulty_write
OOP消息不仅让我定位了出错的地方,更让我惊喜的是,它让我知道了一些秘密:1、gcc中fp到底有何用处?2、为什么gcc编译任何函数的时候,总是要把3条看上去傻傻的指令放在整个函数的最开始?3、内核和gdb是如何知道函数调用栈顺序,并使用函数的名字而不是地址? 4、我如何才能知道各个函数入栈的内容?哈哈,我渐渐喜欢上了让内核惊讶,那就再看一次内核惊讶吧。
执行 cat /dev/faulty,内核又再一次惊讶!
1 Unable to handle kernel NULL pointer dereference at virtual address b
2 pgd = c3a
3 [b] pgd=a, pte=, ppte=
4 Internal error: Oops: [#2] PREEMPT
5 Modules linked in: faulty
6 CPU: 0 Not tainted (2.6..6 #4)
7 PC is at vfs_read0xe0/0×
8 LR is at 0xffffffff
9 pc : [] lr : [] psr:
sp : cd9f ip : c fp : ffffffff
r: r9 : cd r8 :
r7 : r6 : ffffffff r5 : ffffffff r4 : ffffffff
r3 : ffffffff r2 : r1 : cd9f r0 :
Flags: nzCv IRQs on FIQs on Mode SVC_ Segment user
Control: cf Table: a DAC:
Process cat (pid: , stack limit = 0xcd)
Stack: (0xcd9f to 0xcda)
9f: c3ca0 c3c
9f: cd9f cd9fa4 cd9f cf cbb4
9f: befc cc0e4 cd9fa8
9fa0: cbf cf4c befc befc
9fc0: befc
9fe0: befc6c c adab0
Backtrace: invalid frame pointer 0xffffffff
Code: ebffff e e1a da (ec)
Segmentation fault
不过这次惊讶却令人大为不解。OOP竟然说出错的地方在vfs_read(要知道它可是大拿们千锤百炼的内核代码),这怎么可能?哈哈,万能的内核也不能追踪函数调用栈了,这是为什么?其实问题出在faulty_read的行,它导致入栈的r4、r5、r6、fp全部变为了0xffffffff,ip、lr的值未变,这样一来faulty_read函数能够成功返回到它的调用者——vfs_read。但是可怜的vfs_read(忠实的APTCS规则遵守者)并不知道它的r4、r5、r6已经被万恶的faulty_read改变,这样下去vfs_read命运就可想而知了——必死无疑!虽然内核很有能力,但缺少了正确的fp的帮助,它也无法追踪函数调用栈。
ssize_t faulty_read(struct file filp, char __user buf,
size_t count, loff_t pos)
{
int ret;
char stack_buf[4];
memset(stack_buf, 0xff, );
if (count 4)
count = 4;
ret = copy_to_user(buf, stack_buf, count);
if (!ret)
return count;
return ret;
}
:
0: e1a0cd mov ip, sp
4: edd stmdb sp!, { r4, r5, r6, fp, ip, lr, pc}
8: ecb sub fp, ip, #4 ; 0×4
c: edd sub sp, sp, #4 ; 0×4,这里为stack_buf[]在栈上分配1个字的空间,局部变量ret使用寄存器存储,因此就不在栈上分配空间了
: ebc sub r5, fp, # ; 0x1c
: e1a mov r4, r1
: e1a mov r6, r2
1c: e3aff mov r1, # ; 0xff
: e3a mov r2, # ; 0×
: e1a mov r0, r5
: ebfffffe bl //这里在调用memset
: eda ldmia sp, { r3, r4, r5, r6, fp, sp, pc}
这次OOP,深刻地认识到:
内核能力超强,但它不是,也不可能是万能的。所以即使你能力再强,也要和你的team member搞好关系,否则在关键时候你会倒霉的;
出错的是faulty_read,vfs_read却做了替罪羊。所以人不要被表面现象所迷惑,要深入看本质;
内核本来超级健壮,可是你写的驱动是内核的组成部分,由于它出错,结果整体。所以当你加入一个团队的时候一定要告诫自己,虽然你的角色也许并不重要,但你的疏忽大意将足以令整个非常牛X的团队。反过来说,当你是team leader的时候,在选团队成员的时候一定要慎重、慎重、再慎重,即使他只是一个小角色。
工商银行pOS机出现什么意思
你好,工商银行pos机错误代码 设备检测到异常请联系服务商,这个是系统坏了故障了,无法使用请联系工商银行吧!
不知道中了什么病毒?求高人指点!!
识别电脑的病毒:
1、系统病毒
系统病毒的前缀为:Win、PE、Win、W、W等。这些病毒的一般公有的特性是可以感染windows操作系统的 .exe 和 .dll 文件,并通过这些文件进行传播。如CIH病毒。
2、蠕虫病毒
蠕虫病毒的前缀是:Worm。这种病毒的公有特性是通过网络或者系统漏洞进行传播,很大部分的蠕虫病毒都有向外发送带毒邮件,阻塞网络的特性。比如冲击波(阻塞网络),小邮差(发带毒邮件) 等。
3、木马病毒、黑客病毒
木马病毒其前缀是:Trojan,黑客病毒前缀名一般为 Hack 。木马病毒的公有特性是通过网络或者系统漏洞进入用户的系统并隐藏,然后向外界泄露用户的信息,而黑客病毒则有一个可视的界面,能对用户的电脑进行远程控制。木马、黑客病毒往往是成对出现的,即木马病毒负责侵入用户的电脑,而黑客病毒则会通过该木马病毒来进行控制。现在这两种类型都越来越趋向于整合了。一般的木马如QQ消息尾巴木马 Trojan.QQ ,还有大家可能遇见比较多的针对网络游戏的木马病毒如 Trojan.LMir.PSW. 。这里补充一点,病毒名中有PSW或者什么PWD之类的一般都表示这个病毒有**密码的功能(这些字母一般都为“密码”的英文“password”的缩写)一些黑客程序如:网络枭雄(Hack.Nether.Client)等。
4、脚本病毒
脚本病毒的前缀是:Script。脚本病毒的公有特性是使用脚本语言编写,通过网页进行的传播的病毒,如红色代码(Script.Redlof)——可不是我们的老大代码兄哦^_^。脚本病毒还会有如下前缀:VBS、JS(表明是何种脚本编写的),如欢乐时光(VBS.Happytime)、十四日(Js.Fortnight.c.s)等。
5、宏病毒
其实宏病毒是也是脚本病毒的一种,由于它的特殊性,因此在这里单独算成一类。宏病毒的前缀是:Macro,第二前缀是:Word、Word、Excel、Excel(也许还有别的)其中之一。凡是只感染WORD及以前版本WORD文档的病毒采用Word做为第二前缀,格式是:Macro.Word;凡是只感染WORD以后版本WORD文档的病毒采用Word做为第二前缀,格式是:Macro.Word;凡是只感染EXCEL及以前版本EXCEL文档的病毒采用Excel做为第二前缀,格式是:Macro.Excel;凡是只感染EXCEL以后版本EXCEL文档的病毒采用Excel做为第二前缀,格式是:Macro.Excel,依此类推。该类病毒的公有特性是能感染OFFICE系列文档,然后通过OFFICE通用模板进行传播,如:著名的美丽莎(Macro.Melissa)。
6、后门病毒
后门病毒的前缀是:Backdoor。该类病毒的公有特性是通过网络传播,给系统开后门,给用户电脑带来安全隐患。如很多朋友遇到过的IRC后门Backdoor.IRCBot 。
7、病毒种植程序病毒
这类病毒的公有特性是运行时会从体内释放出一个或几个新的病毒到系统目录下,由释放出来的新病毒产生破坏。如:冰河播种者(Dropper.BingHe2.2C)、MSN射手(Dropper.Worm.Smibag)等。
8.破坏性程序病毒
破坏性程序病毒的前缀是:Harm。这类病毒的公有特性是本身具有好看的图标来诱惑用户点击,当用户点击这类病毒时,病毒便会直接对用户计算机产生破坏。如:格式化C盘(Harm.formatC.f)、杀手命令(Harm.Command.Killer)等。
9.玩笑病毒
玩笑病毒的前缀是:Joke。也称恶作剧病毒。这类病毒的公有特性是本身具有好看的图标来诱惑用户点击,当用户点击这类病毒时,病毒会做出各种破坏操作来吓唬用户,其实病毒并没有对用户电脑进行任何破坏。如:女鬼(Joke.Girlghost)病毒。
.捆绑机病毒
捆绑机病毒的前缀是:Binder。这类病毒的公有特性是病毒作者会使用特定的捆绑程序将病毒与一些应用程序如QQ、IE捆绑起来,表面上看是一个正常的文件,当用户运行这些捆绑病毒时,会表面上运行这些应用程序,然后隐藏运行捆绑在一起的病毒,从而给用户造成危害。如:捆绑QQ(Binder.QQPass.QQBin)、系统杀手(Binder.killsys)等。以上为比较常见的病毒前缀,有时候我们还会看到一些其他的,但比较少见,这里简单提一下:
DoS:会针对某台主机或者服务器进行DoS攻击;
Exploit:会自动通过溢出对方或者自己的系统漏洞来传播自身,或者他本身就是一个用于Hacking的溢出工具;
HackTool:黑客工具,也许本身并不破坏你的机子,但是会被别人加以利用来用你做替身去破坏别人。
你可以在查出某个病毒以后通过以上所说的方法来初步判断所中病毒的基本情况,达到知己知彼的效果。在杀毒无法自动查杀,打算采用手工方式的时候这些信息会给你很大的帮助。
landi刷卡机提示
刷卡机故障或系统需要升级。
1、landi刷卡机机显示错误,是刷卡机有小故障,需要关闭重启一下。
2、如重启后还报故障,需要专业人士维修。
pos机错误代码
POS终端号找不到。。1、无此终端号2、请求交易中终端号与应答交易中终端号不匹配3、关联交易中终端号与原始交易中终端号不匹配
arduino如何输出一个数组?如位置坐标(x,y).不是赋值而是输出。
可以试一下把println(x,y);改成:
print("("); print(x); print(","); print(y); println(")");
把数组拆分输出
金三银四Linux内核面试题(最新版)
本文详细整理了常见的 Linux 面试题目,旨在为求职者提供学习参考。以下是总结的关键点:
Linux 是免费和自由传播的 Unix 类似操作系统,广泛用于 Web 项目部署。它具有多用户、多任务、多线程、多 CPU 管理能力,支持 位和 位硬件。Linux 继承了 Unix 的网络为核心设计思想,性能稳定,适合软件开发和部署。
相比之下,Windows 是民用操作系统,侧重娱乐、影音和上网,提供丰富桌面应用和绚丽效果。Linux 则专注于性能,更适合系统优化和特定任务处理。
Unix 和 Linux 在内核、系统架构和权限管理上有显著差异,Linux 在内核源代码可自由下载和修改方面具有优势。
Linux 内核是操作系统的核心,负责管理硬件、软件、内存、文件系统等资源。
Linux 的体系结构分为内核、Shell、GUI、系统实用程序和应用程序等组件,每个方面都具有强大的功能。
Linux 的引导加载程序 LILO 用于将 Linux 操作系统加载到内存中。
BASH 是 Bourne Again Shell 的缩写,作为运行 Linux 系统的默认 Shell,提供更易用的功能。
CLI 是命令行界面,而 GUI 是图形用户界面,它们提供了不同的交互方式。
开源软件的优势在于免费分发、社区协作、功能扩展、错误修复和性能优化。
GNU 项目强调自由软件运动,允许用户自由运行、学习和改进软件,共享给其他人。
简单 Linux 文件系统采用树形结构,根目录为系统最高层次。
内核中的 inode 是文件或目录的索引节点,存储文件元数据。
硬链接和软链接分别用于共享文件的引用和提供文件的软链接路径。
RAID 是磁盘阵列技术,用于提高数据的可靠性、性能或两者。
Linux 系统初始化后,需要进行安全配置,包括设置密码、安装安全补丁、设置防火墙等。
CC 攻击和 DDOS 攻击是针对网站的网络攻击方法,CC 攻击利用多台攻击源同时发起请求,DDOS 则是针对网站流量的攻击。
网站数据库注入是攻击者通过在数据库查询中插入恶意 SQL 代码,以获取敏感数据的攻击手段。
Shell 脚本是用于自动化执行多个命令的文本文件。
选择 Linux 操作系统版本时,应考虑稳定性、安全性和功能需求。
规划 Linux 主机的步骤包括操作系统安装、软件包管理、系统安全配置和应用部署。
处理用户反馈的网站访问慢问题,可能需要进行性能优化、网络配置调整、负载均衡设置和资源监控。
Linux 性能调优方法包括内存管理优化、进程调度策略调整、缓存系统优化和系统参数调优。
基本命令是 Linux 系统操作的基础,涵盖文件管理、进程控制、网络操作等。
Linux 内核锁包括互斥锁、信号量、读写锁等,用于保护共享资源。
用户模式和内核模式分别表示操作系统和应用程序运行的环境。
申请大块内核内存通常使用 alloc_pages 或 _get_free_pages 函数。
用户进程间通信主要方式有信号、共享内存、管道、套接字等。
伙伴系统是一种内存分配策略,用于管理内核内存的分配和回收。
Linux 虚拟文件系统的关键数据结构包括超级块、索引节点、文件描述符和目录入口。
文件或设备的操作函数通常保存在 file_operations 数据结构中。
Linux 中的文件类型包括执行文件、普通文件、目录文件、链接文件和设备文件。
创建进程的系统调用包括 clone、fork、vfork。
进程调度的核心数据结构是 runqueue。
加载和卸载模块使用 insmod 和 rmmod 命令。
模块程序不能使用可链接的库函数,因为它们运行在内核空间。
TLB 缓存的是线性地址到物理地址的映射,加速地址转换过程。
Linux 中的设备类型分为字符设备和块设备,网卡属于例外。
字符设备驱动程序的关键数据结构是 cdev 描述符,用于描述设备。
设备驱动程序的关键功能函数包括 open、read、write、llseek、realse 等。
设备唯一标识通过主设备号和次设备号,使用 dev_t 类型表示。
系统调用通过软件中断实现,允许用户程序请求内核服务。
软中断和工作队列用于中断处理,软中断不能睡眠,而工作队列可以。