linux驱动时间差引发的64位除法问题

推荐一个零声学院免费教程,个人觉得老师讲得不错,
分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,
fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,
TCP/IP,协程,DPDK等技术内容,点击立即学习:

https://ke.qq.com/course/4032547?flowToken=1042705

前言

        原本计算时间差,没想到碰到64位除法的问题,所以啊,就单独拎出来研究研究。

一 简介

        这个内核模块会在卸载时显示从驱动被加载以来经过的时间(以秒为单位)。你将使用位于kernel/time/keeping.c文件中的do_gettimeofday()函数来实现该任务。该函数在被调用时会在timeval数据结构中填充秒和微秒信息。timeval数据结构定义如下:

struct timeval {
      __kernel_time_t         tv_sec;         /* seconds */
      __kernel_suseconds_t    tv_usec;        /* microseconds */
};

本测试例程主要使用了do_gettimeofday()函数,原型和头文件如下:

#include <linux/time.h>
extern void do_gettimeofday(struct timeval *tv);

代码

/*
 * helloworld.c
 *
 *  Created on: 2022年9月14日
 *      Author: lkmao
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/time.h>
#include <linux/types.h>

static int num  =  5;
module_param(num,int, S_IRUGO);

static struct timeval start_time;
static struct timeval end_time;
typedef unsigned long long u64;
static int __init hello_init(void)
{
	int a = 5;
	int b = 10;
	int mod = 0;
	mod = do_div(b,a);
	printk("mod = %d a = %d,b =%d\n",mod,a,b);
	printk(KERN_ERR"hello world num = %d\n",num);
	pr_info("hello");
	do_gettimeofday(&start_time);
	return 0;
}
static void __exit hello_exit(void)
{
	u64 diff_us;
	u64 ms = 0;
	printk("goodbye\n");
	do_gettimeofday(&end_time);
	diff_us = (((u64)end_time.tv_sec - (u64)start_time.tv_sec)*1000000) + \
			((u64)end_time.tv_usec - (u64)start_time.tv_usec);
	ms = diff_us;
	 do_div(ms,1000);
	printk("diff_us = %llu \n",diff_us);
	printk("ms = %llu\n",ms);

}
module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");



Makefile

export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
KERN_DIR = /home/lkmao/imx/linux/linux-imx

$(info $(KERN_VER))
obj-m += helloworld.o

all:
	rm helloworld.ko -f
	$(MAKE) -C $(KERN_DIR) M=`pwd` modules
	cp helloworld.ko /home/lkmao/nfsroot/server/ -f
	ls /home/lkmao/nfsroot/server/ -ls
	
cp:
	sudo cp *.ko /home/lkmao/nfsroot/buildrootfs/driver_test/
	sudo cp app /home/lkmao/nfsroot/buildrootfs/driver_test/
	sudo cp *.ko /home/lkmao/nfsroot/buildrootfs_udev/driver_test/
	sudo cp app /home/lkmao/nfsroot/buildrootfs_udev/driver_test/
	#scp *.ko root@192.168.0.3:/
#	arm-linux-gnueabihf-gcc test.c -o app
	#$(MAKE) -C $(KERN_DIR) M=$(shell pwd) modules

app:
	arm-linux-gnueabihf-gcc test.c -o app
.PHONY: clean
clean:
	$(MAKE) -C$(KERN_DIR) M=$(shell pwd) clean
	rm -rf app

测试

挂载nfs

在开发板中挂载ubuntu主机的nfs目录:

mount -t nfs -o nolock 192.168.0.11:/home/lkmao/nfsroot/server /server

运行结果

rmmod的时候,就可以看到这个结果。

[ 3885.409188] goodbye
[ 3885.411513] diff_us = 8043635
[ 3885.415345] ms = 8043

__aeabi_uldivmod报错的问题

编译的时候警告

WARNING: "__aeabi_uldivmod" [/home/lkmao/workspace/hello_driver/helloworld.ko] undefined!

insmod的时候报错:

root@host:/server# insmod helloworld.ko
[  141.481187] helloworld: Unknown symbol __aeabi_uldivmod (err 0)
[  141.514301] helloworld: Unknown symbol __aeabi_uldivmod (err 0)
insmod: can't insert 'helloworld.ko': unknown symbol in module, or unknown parameter
root@host:/server# 

是编译器优化的锅吗?

解决方案:内核的顶层Makefile文件中,查找KBUILD_CFLAGS   变量,并在其中添加

-fno-tree-scev-cprop

修改完成后的值如下所示:

 重新编译内核。

然后再重新编译模块。重新测试,并没有解决问题。去掉上面的修改。

其实是64位除法的问题

然后看下代码中出问题的地方,把这行代码注释掉,编译通过了,它能有什么问题呢?

printk("diff_us = %llu second = %llu ms = %llu",diff_us,diff_us/1000000,diff_us/1000);

经过反复折腾,发现原来问题出在除法上了,原来32bit的系统对64bit的变量的触发操作是有要求的。

是要使用do_div函数。

写一段代码测试这个宏函数,它的返回值是余数。关键是除数怎么得到的

        int a = 5;
        int b = 10;
        int mod = 0;
        mod = do_div(b,a);
        printk("mod = %d a = %d,b =%d\n",mod,a,b);

运行结果如下

[ 2738.585889] mod = 0 a = 5,b =2
[ 2738.588970] hello world num = 5

从结果可以,第一个参数变成了计算值。

do_div宏的定义

#define do_div(n, base) __do_div_asm(n, base)

在找__do_div_asm,作为汇编小白,看不懂啊。n  = __res 这行明白,就是结果赋值给了第一个参数。

#define __do_div_asm(n, base)					\
({								\
	register unsigned int __base      asm("r4") = base;	\
	register unsigned long long __n   asm("r0") = n;	\
	register unsigned long long __res asm("r2");		\
	register unsigned int __rem       asm(__xh);		\
	asm(	__asmeq("%0", __xh)				\
		__asmeq("%1", "r2")				\
		__asmeq("%2", "r0")				\
		__asmeq("%3", "r4")				\
		"bl	__do_div64"				\
		: "=r" (__rem), "=r" (__res)			\
		: "r" (__n), "r" (__base)			\
		: "ip", "lr", "cc");				\
	n = __res;						\
	__rem;							\
})

总结

        不错,今天又遇到一个问题,并解决了这个问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千册

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值