驱动调试之打印

发布时间:2024-08-19  

在编写驱动过程分析中会遇到许多难找的问题,这时候调试的方法就很重要了,下面介绍的是利用打印的方法调试驱动,这种方法同样可以用在应用的调试过程中,而且很有效。


1、prink的原理


首先介绍一下打印的函数prink的原理,printk的原理是最终打印在终端上的。所以只要是能成为终端的设备均可被打印,比如串口、网络、LCD等等。


在u-boot的启动参数中,有这么一项console=ttySAC0,其中ttySAC0就是最终printk打印到的设备。


bootargs=console=ttySAC0 root=/dev/nfs nfsroot=192.168.1.101:/work/nfs_andy/first_fs ip=192.168.1.18:192.168.1.101:192.168.1.1:255.255.255.0::eth0:off

 


1.1、__setup调用过程分析


为了分析prink,可以在内核源码中搜索“console=”,最终可以在在kernelprintk.c中找到__setup('console=', console_setup);这种形式的定义在Linux内核源码阅读记录一之分析存储在不同段中的函数调用过程中已经介绍过,下面介绍一遍调用过程,__setup被定义在includelinuxinit.h中:


171 #define __setup(str, fn)                   

172     __setup_param(str, fn, fn, 0)


160 #define __setup_param(str, unique_id, fn, early)           

161    static char __setup_str_##unique_id[] __initdata = str;   

162     static struct obs_kernel_param __setup_##unique_id   

163        __attribute_used__               

164        __attribute__((__section__('.init.setup')))   

165        __attribute__((aligned((sizeof(long)))))   

166        = { __setup_str_##unique_id, fn, early }



148 struct obs_kernel_param {

149    const char *str;

150    int (*setup_func)(char *);

151    int early;

152 };


最终把__setup('console=', console_setup);展开可以得到:从展开的函数可以知道,最终定义了一个位于.init.setup段的结构体__setup_console_setup,并且初始化了它的各个成员,有函数,有名称等等。


static char __setup_str_console_setup[] __initdata = 'console=';  


 static struct obs_kernel_param __setup_console_setup    

 __attribute_used__                

 __attribute__((__section__('.init.setup')))   

  __attribute__((aligned((sizeof(long)))))   

    __setup_str_console_setup, 

    console_setup,

     0

}


接着在arch/arm/kernel/vmlinux.lds中搜索.init.setup,可以得到这个段的初始化地址与结束地址__setup_start、__setup_end


  __setup_start = .;

   *(.init.setup)

  __setup_end = .;

为了得到调用这个段的时机,我们继续接着在内核源码中搜索__setup_start,在initmain.c中的obsolete_checksetup函数搜索到了它,obsolete_checksetup这个函数的第6行开始会根据__setup_console_setup结构体中的str字符串值与传入的line字符串值是否相等以及early参数来决定是否调用__setup_console_setup结构体中的函数。


 1 static int __init obsolete_checksetup(char *line)

 2 {

 3     struct obs_kernel_param *p;

 4     int had_early_param = 0;

 5 

 6     p = __setup_start;//.init.setup的首地址

 7     do {

 8         int n = strlen(p->str);

 9         if (!strncmp(line, p->str, n)) {//在.init.setup中寻找相符的命令行参数

10             if (p->early) {//如果early大于0,那么这个参数在前面已经处理过了

11                 /* Already done in parse_early_param?

12                  * (Needs exact match on param part).

13                  * Keep iterating, as we can have early

14                  * params and __setups of same names 8( */

15                 if (line[n] == '' || line[n] == '=')

16                     had_early_param = 1;

17             } else if (!p->setup_func) {//如果处理函数不存在,则报错

18                 printk(KERN_WARNING 'Parameter %s is obsolete,'

19                        ' ignoredn', p->str);

20                 return 1;

21             } else if (p->setup_func(line + n))//调用处理函数处理

22                 return 1;

23         }

24         p++;

25     } while (p < __setup_end);

26 

27     return had_early_param;

28 }


到这里我们直接罗列出obsolete_checksetup的调用过程,还是从start_kernel开始:


1 start_kernel

2     parse_args('Booting kernel', static_command_line, __start___param, __stop___param - __start___param,&unknown_bootoption);//后续的命令处理

3         unknown_bootoption

4             obsolete_checksetup

继续往下分析parse_args函数,这个函数会找出命令参数,然后调用parse_one函数处理


int parse_args(const char *name,

           char *args,

           struct kernel_param *params,

           unsigned num,

           int (*unknown)(char *param, char *val))

{

    char *param, *val;


    DEBUGP('Parsing ARGS: %sn', args);


    /* Chew leading spaces */

    while (*args == ' ')

        args++;


    while (*args) {//循环处理剩余的命令行,直到全部处理完成

        int ret;

        int irq_was_disabled;


        args = next_arg(args, ¶m, &val);//找出下一个命令行参数*param为命令名称,*val为参数值

        irq_was_disabled = irqs_disabled();

        ret = parse_one(param, val, params, num, unknown);//处理param为

        if (irq_was_disabled && !irqs_disabled()) {

            printk(KERN_WARNING 'parse_args(): option '%s' enabled '

                    'irq's!n', param);

        }

        switch (ret) {

        case -ENOENT:

            printk(KERN_ERR '%s: Unknown parameter `%s'n',

                   name, param);

            return ret;

        case -ENOSPC:

            printk(KERN_ERR

                   '%s: `%s' too large for parameter `%s'n',

                   name, val ?: '', param);

            return ret;

        case 0:

            break;

        default:

            printk(KERN_ERR

                   '%s: `%s' invalid for parameter `%s'n',

                   name, val ?: '', param);

            return ret;

        }

    }


    /* All parsed OK. */

    return 0;


继续分析parse_one函数,可以看到,最终调用了obsolete_checksetup处理函数。在obsolete_checksetup会处理相应的命令行参数


static int parse_one(char *param,

             char *val,

             struct kernel_param *params, 

             unsigned num_params,

             int (*handle_unknown)(char *param, char *val))

{

    unsigned int i;


    /* Find parameter */

    for (i = 0; i < num_params; i++) {//从__param段找出与命令行参数相同的名字

        if (parameq(param, params[i].name)) {

            DEBUGP('They are equal!  Calling %pn',

                   params[i].set);

            return params[i].set(val, ¶ms[i]);//如果是内核的参数,那么直接传给内核参数,然后返回。

        }

    }


    if (handle_unknown) {//如果不是内核的参数,并且处理函数存在

        DEBUGP('Unknown argument: calling %pn', handle_unknown);

        return handle_unknown(param, val);//调用处理函数处理

    }


    DEBUGP('Unknown argument `%s'n', param);

    return -ENOENT;

}


1.2、控制台设置函数console_setup分析


介绍完了__setup的调用过程吗,接下来分析console_setup函数:


static int __init console_setup(char *str)

{

    char name[sizeof(console_cmdline[0].name)];

    char *s, *options;

    int idx;


    /*

     * Decode str into name, index, options.

     */

    if (str[0] >= '0' && str[0] <= '9') {//如果以数字0-9开头

        strcpy(name, 'ttyS');

        strncpy(name + 4, str, sizeof(name) - 5);

    } else {

        strncpy(name, str, sizeof(name) - 1);//将str拷贝到name中,去除结束符

    }

    name[sizeof(name) - 1] = 0;

    if ((options = strchr(str, ',')) != NULL)//如果参数中存在,的话。说明带波特率参数

        *(options++) = 0;

#ifdef __sparc__

    if (!strcmp(str, 'ttya'))

        strcpy(name, 'ttyS0');

    if (!strcmp(str, 'ttyb'))

        strcpy(name, 'ttyS1');

#endif

    for (s = name; *s; s++)

        if ((*s >= '0' && *s <= '9') || *s == ',')

            break;

    idx = simple_strtoul(s, NULL, 10);//取出波特率参数,转换成整形

    *s = 0;


    add_preferred_console(name, idx, options);//将参数保存在console_cmdline中

    return 1;

}


在console_setup这个函数的最后会调用console_cmdline将参数保存在console_cmdline中。假设我想用名为'ttySAC0'的控制台,先记录下来放到console_cmdline这个全局变量中,接着搜索'console_cmdline'可以找到register_console这个函数。


分析到这里,大胆的假设,如果我想要printk打印到某个设备上,那么这个设备需要调用register_console注册控制台,并且注册的名字需要与命令行参数传入的名字相同。为了验证这个假设,接着搜索register_console这个函数,看看有哪些设备驱动调用了它。在driversserials3c2410.c文件中找到了它:可以看到,s3c24xx_serial_console 结构体中的名字与命令行传入的名字相符,所以prink最终可以调用s3c24xx_serial_console结构体中的write函数打印到串口输出。

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>