前言
本文主要是介绍 Linux 内核提供的关于32位与64位除法已经实现的接口,使得在 Linux 中需要除法运算或编译中遇到如上问题时能正确解决问题。
一、问题
在编译 Linux 代码时,经常会遇到如下错误:
undefined symbol ‘udivdi3’
undefined symbol 'divid3'
二、解决方法
1.根本原因描述
在嵌入式中,32位系统中(目前多数系统都是,比如 ARM),对于普通的 a 除以 b(b 为32位):
(1)当 a 为32位,Linux 内核中,常用 uint32_t 类型,可以直接写为 a / b;
(2)但是,对于 a 是64位,uint64_t 的时候,就要用到专门的除操作相关的函数,linux 内核里面一般为 do_div(n, base),注意,此处do_div得到的结果是余数,而真正的 a / b 的结果,是用 a 来保存的。
do_div(n,base) 的具体定义,和当前体系结构有关,所以其具体实现在相关架构目录下的 <asm/div64.h>,其实现较为复杂,我也没看懂。
因此,如果你当前写代码,a / b,如果 a 是uint64_t类型,那么一定要利用do_div(a,b),而得到结果 a,
而不能简单的用 a / b,否则编译可以正常编译,但是最后链接最后出错,会提示上面的那个错误:
undefined reference to “__udivdi3”
2.解决方法
知道导致该错误的根本原因之后,就可以在代码中找到对应的用到除法
的地方
即类似于 a / b
的地方,其中被除数 a 为64位,Linux中一般用用 uint64_t
,将a / b
用 do_div(a,b)
得到的 a
去代替(注意,不是直接用 do_div() 得到真正 a 除 b 后的结果,因为 do_div(a, b) 得到的是余数)
三、总结学习
1.do_div 函数
#define do_div(n, base) __do_div_asm(n, base)
#define __do_div_asm(n, base) [省略]
函数功能:
实现在32位处理器中实现64位除法,需要包含头文件 <asm/div64.h>
可以将其实现理解为:
ret = n % base;
n /= base;
函数参数:
n: uint64_t 的被除数
base: 32位的除数
函数返回值:
被除数除以除数的余数。
2.Linux 内核实现的64位除法函数
Linux 内核封装好了支持64位除法的函数,包含 unsigned 和 signed 两类,具体函数如下,需要包含头文件 < linux/math64.h >
#if BITS_PER_LONG == 32 //针对32位处理器
(1)div_u64
/*
* unsigned 64位除法,不需要的得到余数
* Param - u64 : 被除数
* Param - u32 : 除数
* Return - u64 : 除后的结果
*/
static inline u64 div_u64(u64 dividend, u32 divisor)
{
u32 remainder;
return div_u64_rem(dividend, divisor, &remainder);
}
/*
* unsigned 64位除法,需要的得到余数
* Param - u64 : 被除数
* Param - u32 : 除数
* Param - u32* : 除后的余数
* Return - u64 : 除后的结果
*/
static inline u64 div_u64_rem(u64 dividend, u32 divisor, u32 *remainder)
{
*remainder = do_div(dividend, divisor);
return dividend;
}
(2)div_s64
/*
* signed 64位除法,不需要的得到余数
* Param - s64 : 被除数
* Param - s32 : 除数
* Return - s64 : 除后的结果
*/
static inline s64 div_s64(s64 dividend, s32 divisor)
{
s32 remainder;
return div_s64_rem(dividend, divisor, &remainder);
}
/*
* signed 64位除法,需要的得到余数
* Param - s64 : 被除数
* Param - s32 : 除数
* Param - s32* : 除后的余数
* Return - s64 : 除后的结果
*/
s64 div_s64_rem(s64 dividend, s32 divisor, s32 *remainder)
{
u64 quotient;
if (dividend < 0) {
quotient = div_u64_rem(-dividend, abs(divisor), (u32 *)remainder);
*remainder = -*remainder;
if (divisor > 0)
quotient = -quotient;
} else {
quotient = div_u64_rem(dividend, abs(divisor), (u32 *)remainder);
if (divisor < 0)
quotient = -quotient;
}
return quotient;
}
EXPORT_SYMBOL(div_s64_rem);