ldd3学习笔记——通过查询调试之/proc文件系统

零、写在开头的话

  在这里整理一些关于ldd3这本书的学习笔记,由于才开始学习,故此这里写下的是自己的理解,可能会有一些偏差,不过学习是积累的过程,后面学习有了新的发现再回过头来进行修改,也是可以的。

  使用CSDN博客来记录是因为可以有较好的格式,在代码的表现上比Word好太多了,并且可以得到保存和分类。话不多说,开始整理一些内容

一、通过查询方式调试

  在ldd3中,查询调试方法的前一节为打印调试。在该节中使用了printk来进行调试,为了得到足够的信息来判断错误,需要大量的使用该语句,导致的结果是系统性能显著降低。所以这一节提供了更好的方法——在需要的时候才去查询系统信息,而不是持续不断的产生数据。

  本节介绍了两种查询调试方法,一种是在/proc文件系统中创建文件,另一种是使用驱动程序的ioctl方法。这里主要记录在实际操作过程中,对于第一种方法的理解和遇到问题的整理。

二、/proc文件系统

  首先简单介绍一下/proc文件系统,其实一种特殊的、由软件创建的文件系统,内核使用它向外界导出信息。/proc下面每个文件都绑定于一个内核函数,用户读取其中的文件时,该函数动态地生成文件的“内容”。因为其文件系统时动态的,所以驱动程序模块可以在任何时候添加或删除其中的入口项。当然,/proc文件不仅可以用于读出数据,也可以用于写入数据,本节只是简单涉及只读情形。

在/proc中实现文件

  所有使用/proc的模块必须包含头文件<linux/proc_fs.h>,为了在/proc目录下创建一个只读文件,我们需要在设备的驱动程序中实现一个函数,该函数的作用是:在使用命令读取/proc目录下的文件时,生成数据。具体来说,就是当某个进程读取1/proc目录下的只读文件时,该读取请求会通过函数发送到驱动程序模块,而驱动程序将所需要的数据通过一个内存页返回到用户空间,这个内存页是在某进程读取/proc文件时,由内核分配的(即PAGE_SIZE字节的内存块)。

通过该函数发送到驱动程序模块
通过内存页
开始
读取/proc下文件
内存分配内存页
驱动程序将数据返回用户空间

这个函数称为read_proc方法:

int (*read_proc) (char *page, char **start, off_t offset, int count, int *eof, void *data);

函数的参数含义按照书上内容抄写如下:

  参数表中page指针指向用来写入数据的缓冲区;使用二重指针start返回实际的数据写到内存页的哪个位置;参数count是请求传输的数据长度;offset是一个指向长偏移量类型对象的指针,用来指明用户在文件中进行存取操作的位置;参数eof指向一个整型数,在没有数据可返回时,驱动程序必须设置这个参数;data参数是提供给驱动程序的专用数据指针,可用于内部记录。

  书上这里给出了一个简单的read_proc()函数的实现,后面给出了一个更好的方法实现/proc文件,为seq_file()函数,本篇笔记首先实现第一种函数。scull设备read_proc()函数的简单实现如下代码:

int scull_read_procmem(char *buf, char **start, off_t offset,int count, int *eof, void *data)
{
	int i, j, len = 0;
	int limit = count - 80; /* Don't print more than this */
	for (i = 0; i < scull_nr_devs && len <= limit; i++) 
	{
		struct scull_dev *d = &scull_devices[i];
		struct scull_qset *qs = d->data;
		if (down_interruptible(&d->sem))
			return -ERESTARTSYS;
		len += sprintf(buf+len,"\nDevice %i: qset %i, q %i, sz %li\n",i, d->qset, d->quantum, d->size);
		for (; qs && len <= limit; qs = qs->next) 
		{ /* scan the list */
			len += sprintf(buf + len, "  item at %p, qset at %p\n",qs, qs->data);
			if (qs->data && !qs->next) /* dump only the last item */
			for (j = 0; j < d->qset; j++) 
			{
				if (qs->data[j])
				len += sprintf(buf + len,"    % 4i: %8p\n",j, qs->data[j]);
			}
		}
		up(&scull_devices[i].sem);
	}
 	*eof = 1;
 	return len;
}

上面函数是一个相当典型的read_proc实现,它假定决不会有生成多于一页的数据的需求,因此忽略了startoffset值。但是,要小心不要超出缓冲区,以防万一。

创建自己的/proc文件

  定义好的read_proc函数可以将数据传入缓冲区,而将其与一个/proc入口项连接起来,这便需要create_proc_read_entry来实现,并在卸载模块时,使用函数remove_proc_entry来删除/proc中的入口项。

  以下是scull设备调用该函数创建/proc文件的代码:

create_proc_read_entry("scullmem", 0 /* default mode */,NULL /* parent dir */,scull_read_procmem,NULL /* client data */);

  删除时的代码如下:

remove_proc_entry("scullmem", NULL /* parent dir */);

三、具体实现

  上面是ldd3书本上的知识,在实际操作过程中,会有一些细小的地方需要规范或者注意,这里记录下实际中所遇到的一些问题。

  首先对于源代码做以说明:笔者在网上下载到ldd3配套的源程序,但是发现其程序一劳永逸的将scull设备的所用内容全部编写进去,故笔者在使用时,按照ldd3上的编写顺序,以及参照下载得来的源程序的编写规范,编写了仅实现scull设备基础功能的代码,并根据学习的进度,随时在该代码上添加内容。所以这里的具体实现是指在笔者自己(或按照书本顺序)的基础代码上对于/proc文件的简单实现。

read_proc()函数的实现

  此函数在书中已经给出,直接添加到程序当中即可。所需注意的是在基础程序(仅实现scull设备基本功能的代码)中,是不含DEBUG功能的,而为了方便打开和关闭调试功能,使用了#ifdef预编译命令,故在使用read_proc()函数时先定义DEBUG,具体代码如下:

#ifdef SCULL_DEBUG/* use proc only if debugging */
/*
 * The proc filesystem: function to read and entry
 */
int scull_read_procmem(char *buf, char **start, off_t offset,int count, int *eof, void *data)
{
	代码内容上文已有
}

createremove函数的实现

  即为创建目录入口项和卸载删除入口项的函数,其主要语句上文(ldd3书中)以及给出,所需要所得仅是定义一个函数即可,具体代码如下:

/*
 * Actually create (and remove) the /proc file(s).
 */
 static void scull_create_proc(void)
{
	create_proc_read_entry(上文已有);
}
static void scull_remove_proc(void)
{
	remove_proc_entry(上文已有);
}
#endif /* SCULL_DEBUG */(这里和read_proc程序前的#ifdef相对应)

  到这里仅是定义了创建和删除两个函数,并未使用,需要实现在安装模块时创建,在卸载模块时删除的功能,因此需要将这两个函数分别与安装模块和卸载模块语句里调用一次、同样因方便开启和关闭调试的缘故,需使用预编译。具体语句如下:

int scull_init_module(void)
{
	...
#ifdef SCULL_DEBUG /* only when debugging */
 	scull_create_proc();
#endif
	...
void scull_cleanup_module(void)
{
	...
#ifdef SCULL_DEBUG /* use proc only if debugging */
	scull_remove_proc();
#endif
	...
}

  以上即是在scull.c源程序上的改动,在scull.h这个头文件中,需要增加SCULL_DEBUG这个宏定义,如此即可完成/proc文件系统的添加。

实际调试

  在编写完成,make编译通过之后,运行脚本sh scull_load完成设备安装,之后执行命令,查询/proc目录下只读文件,命令为:cat /proc/scullmem,发现并未顺利完成功能,出现错误如下图所示:
报错,使用了NULL指针
接上
在这里插入图片描述
  在图中,可以看到如EIP is at __down_interruptible+0x56/0x11f的字样,从这里我们可以看出,故障所在的函数是down_interruptible,若是语句最后有中括号,则表明该函数位于括号里的模块内,十六进制的数据表明指令指针在该函数的56字节处,而函数本身是11f字节长。浏览代码,该语句定位到下面函数:

int scull_read_procmem(char *buf, char **start, off_t offset,int count, int *eof, void *data)
{
	...
  	if (down_interruptible(&d->sem))
   		return -ERESTARTSYS;

在将该语句注释掉后,再次编译执行,不在报错,显示如下图所示:
在这里插入图片描述
在写入一些数据到scull0设备中后,再次执行可以观察该只读文件工作正常,如下图所示:
在这里插入图片描述

后记

1、对于down_interruptible语句为何会报错暂时还未了解,待解决后给出方案

2、卸载设备时,内核崩溃……


  1. 读取操作使用read系统调用 ↩︎

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值