4.3查询调试

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/shenwanjiang111/article/details/81902555

4.3 查询调试


驱动程序开发者可以使用一些查询技术来调试程序,比如说:

  • /proc创建文件;
  • 使用驱动程序的ioctl方法;
  • sysfs导出属性;

使用sysfs需要有驱动程序模型的背景知识,所以这将在第14章讨论。


4.3.1 /proc


/proc文件系统是一个特殊的,由软件创建的文件系统,内核用它将信息导出到外界。/proc下的每个文件都与内核函数绑定在一起,当用户读取其中的文件时,该函数动态地生成文件的“内容”。例如,/proc/modules总是返回当前已加载模块的列表。

Linux系统中,大量使用/proc。许多工具,比如pstopuptime,都是通过/proc获取它们所需的信息。设备驱动程序当然也可以从/proc导出信息。/proc文件系统是动态的,因此模块程序可以随时添加、删除文件。

完整的/proc文件是非常复杂的,并且是可读写的。但是大部分时候,/proc中的文件是只读的。本文中,假设都是只读的。如果想要实现更为复杂的/proc文件,请参阅linux内核源代码。

但是,不鼓励在/proc下添加文件,推荐使用方式是sysfs。但是,使用sysfs需要了解Linux设备模型,这个到第14章,我们才会讨论。同时,在/proc中创建文件又是非常简易的,且符合调试的目的,因此,我们先讨论它。


4.3.1.1 /proc实现文件


所有使用/proc的模块,必须包含头文件<linux/proc_fs.h>

为了创建一个只读的/proc文件,驱动程序必须实现一个函数,当该文件被读时,产生数据。当某个进程读这个文件时(调用read系统调用),请求通过该函数送达给驱动程序。首先,看一下这个函数,稍后再看其注册接口。

当进程从创建的/proc文件中读取时,内核会分配一页内存(即PAGE_SIZE字节),驱动程序可以在其中写入要返回给用户空间的数据。该缓冲区被传递给名为read_proc的函数:

int (*read_proc)(char *page, char **start, off_t offset, int count, int *eof, void *data);
参数说明:
  • page

    是要写入数据的缓冲区;

  • start

    说明感兴趣的数据在页面中写入的位置(稍后会详细介绍);

  • offset

    含义与read方法相同。

  • count

    含义与read方法相同。

  • eof

    指向一个整数的指针,驱动程序必须设置该整数以表明它没有更多数据要返回;

  • data

    是指向驱动程序特定数据的指针,可用于用户记录。

这个函数返回写入到page缓冲区内的数据的字节数,就像read方法一样。eof是一个简单的标志,但是start的使用比较复杂;它的目的就是帮助实现更大的/proc文件(大于一页)。

参数start有一些非常规的用途。它的目的是指出要在哪里(页面内)找到要返回给用户的数据。当proc_read方法被调用时,*start设为NULL。如果将其设为NULL,则内核假定数据从偏移量为0的地址开始写入,就像参数offset等于0一样;换句话说,就是忽略掉参数offset,不论其值为多少。相反,如果将*start设为非NULL值,则内核假定*start+offset为开始地址,然后将数据返回给用户。一般来说,只有少量的数据的话,直接忽略掉参数start就好。更复杂的方法考虑startoffset参数配合使用。

参数start可以解决长久以来困扰/proc文件的一个主要问题。有时候,连续调用read系统调用时,ASCII表示的内核数据结构会发生变化,读取文件的进程会发现两次调用之间的数据不一致。如果*start被设为一个较小值,用它来增加filp->f_pos,然后据此读取数据,不再需要返回的数据量,从而使f_pos成为read_proc过程的内部记录号。

例如,如果read_proc函数正在从大型结构数组中返回信息,并且在第一次调用中返回了其中5个结构,则可以将* start设置为5。下一次调用将提供与偏移量相同的值; 然后驱动程序知道开始从数组中的第六个结构返回数据。可以在fs/proc/generic.c中看到。

请注意,有更好的方法来实现大型/proc文件; 它被称为seq_file,我们很快就会讨论它。 首先,先看一下普通的/proc的示例。 这是一个简单的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; /* 打印不能超过这个大小 */

    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。 但是,小心不要超出缓冲区,以防万一。

猜你喜欢

转载自blog.csdn.net/shenwanjiang111/article/details/81902555
4.3