手写简易操作系统(二十二)--时间管理

前情提要

上一节我们实现了硬盘的驱动,本来这一节打算实现文件系统的,但是文件系统中有个时间属性,所以这里我们先实现操作系统的时间管理。

一、除法

我们的系统是一个32位的系统,在编译一些除法的时候编译的时候没问题,链接的时候就会出现提示

undefined symbol ‘udivdi3’
undefined symbol 'divid3'

这是由于在除法 a/b 中,如果 a 是六十四位的,就需要用到专门的除法函数,在32位系统下,CPU无法处理这样的信息。GCC编译器找不到对应的除法函数。

为了解决这个问题,在linux中,<linux/math.h> 中提供了这样的一个函数,帮助在32位机下做64位的除法运算

image-20240331204034582

1.1、仿真实现

由于上述的实现设计了大量的汇编原理,所以这里我们用C实现一个可以做64位除法的函数

uint64_t divide_u64_u32(uint64_t dividend, uint32_t divisor, uint64_t* remainder) {// 检查除数是否为0if (divisor == 0) {return 0; // 除数为0,返回0}// 用于存储商的变量uint64_t quotient = 0;// 从最高位开始逐步计算商for (int i = 63; i >= 0; i--) {// 将余数左移1位*remainder <<= 1;// 获取当前位的值uint64_t bit = (dividend >> i) & 1;// 将当前位的值加到余数的最低位*remainder |= bit;// 如果余数大于等于除数,则减去除数,并将对应位的商置1if (*remainder >= divisor) {*remainder -= divisor;quotient |= (1ULL << i);}}return quotient; // 返回商
}
  1. uint64_t divide_u64_u32(uint64_t dividend, uint32_t divisor, uint64_t* remainder) {: 这是函数的声明,它接受一个 64 位无符号整数 dividend 作为被除数,一个 32 位无符号整数 divisor 作为除数,以及一个指向 64 位无符号整数的指针 remainder,用于存储余数。
  2. if (divisor == 0) { return 0; }: 检查除数是否为零,如果除数为零,直接返回 0,因为除数为零时无法进行除法运算。
  3. uint64_t quotient = 0;: 创建一个 64 位无符号整数变量 quotient,用于存储商的值。
  4. for (int i = 63; i >= 0; i--) {: 使用循环从被除数的最高位开始逐位计算商。
  5. *remainder <<= 1;: 将余数左移 1 位,为下一次计算商做准备。
  6. uint64_t bit = (dividend >> i) & 1;: 获取被除数的当前位的值(0 或 1)。
  7. *remainder |= bit;: 将被除数的当前位的值加到余数的最低位,相当于将被除数向左移动 1 位。
  8. if (*remainder >= divisor) { *remainder -= divisor; quotient |= (1ULL << i); }: 如果余数大于等于除数,则减去除数,并将对应位的商置 1。
  9. return quotient;: 返回计算得到的商。

这段代码实现了一种基于位操作的除法算法,称为 “恒定除法” 或 “restoring division”。

恒定除法(Restoring Division)是一种用于实现除法运算的基本算法之一。其主要原理如下:

  1. 初始化:首先,将除数和被除数进行比较,确定它们的相对大小,并对商和余数进行初始化。将被除数存储在一个寄存器中,将商存储在另一个寄存器中,余数初始为零。
  2. 逐位相除:从被除数的最高有效位(MSB)开始,逐位地与除数进行比较。在每一步中,将被除数左移一位,并将下一位的值移入余数的最低位。这相当于模拟手动长除法中的将下一位数附加到当前余数上的步骤。
  3. 检查余数与除数的关系:在每个步骤中,与除数相比较余数。如果余数大于或等于除数,则表示可以从余数中减去一个除数。如果余数小于除数,则不能从余数中减去一个除数。
  4. 减去除数并更新商:如果余数大于或等于除数,则从余数中减去除数,并将商的当前位设置为 1。否则,将商的当前位设置为 0。
  5. 重复步骤:重复上述步骤,直到对被除数的所有位进行了处理。
  6. 得到商和余数:当处理完所有位时,商的值即为除法的结果,而最后剩下的余数则是除法的余数。

1.2、汇编实现

上面虽然实现了64位的除法,但是效率不高,下面我们使用Linux内置的一种方法实现

// mod操作
#define do_mod(n, base)						\
({								\unsigned long __upper, __low, __high, __mod, __base;	\__base = (base);					\asm("" : "=a" (__low), "=d" (__high) : "A" (n));\__upper = __high;				\if (__high) {					\__upper = __high % (__base);		\__high = __high / (__base);	                	\}						                            \asm("divl %2" : "=a" (__low), "=d" (__mod)	        \: "rm" (__base), "0" (__low), "1" (__upper));	\asm("" : "=A" (n) : "a" (__low), "d" (__high));	    \__mod;							\
})// div操作
#define do_div(n, base)	({								\unsigned long __upper, __low, __high, __quotient, __base = (base);\asm("" : "=a" (__low), "=d" (__high) : "A" (n));\__upper = __high;				\if (__high) {					\__upper = __high % (__base);		\__high = __high / (__base);	                	\}						                            \asm("divl %2" : "=a" (__quotient), "=d" (__low)	        \: "rm" (__base), "0" (__low), "1" (__upper));	\asm("" : "=A" (n) : "a" (__low), "d" (__high));	    \__quotient;							\
})

另一种也是C语言实现

/* 实现六十四位整形的除法 */
uint64_t div_u64_rem(uint64_t dividend, uint32_t divisor, uint32_t* remainder) {// 定义一个联合体以将64位被除数视为两个32位部分union {uint64_t v64;uint32_t v32[2];} d = { dividend };uint32_t upper;// 存储被除数的高32位upper = d.v32[1];d.v32[1] = 0;// 检查被除数的高部分是否大于或等于除数if (upper >= divisor) {// 计算高部分的商和余数d.v32[1] = upper / divisor;upper %= divisor;}// 使用除数对低32位进行除法运算asm("divl %2" : "=a" (d.v32[0]), "=d" (*remainder) :"rm" (divisor), "0" (d.v32[0]), "1" (upper));// 返回组合的64位结果return d.v64;
}

以上的三种方法都写到了 math.c 中,这三种方法都实现了 64 位整型除以 32 位整型无法连接的问题。

二、COMS

系统的时间是保存在COMS中的,读取COMS即可读取到开机时的时间,但是时间是一直在流逝的,每次读取时间都是读取COMS的话,慢,浪费时间。所以后面的时间更新就是开机时间加上我们在定时器中的滴答数算出来的。我们的滴答数是一秒100次,所以正好是一次10ms。

2.1、CMOS

CMOS(互补金属氧化物半导体)是一种集成电路技术,常用于制造微处理器、存储器和其他数字逻辑电路。在计算机中,CMOS被广泛用于实现非易失性存储器,以保存系统的基本设置和配置信息,包括日期、时间、启动顺序等。

CMOS存储器通常位于主板上,由一块称为CMOS RAM(随机存取存储器)的小型电池供电的芯片中。这个电池通常是一块纽扣电池,它持续为CMOS芯片提供电力,即使计算机关闭也能保持存储器中的数据。

在计算机开机时,系统会读取CMOS中的数据,并将其加载到内存中。这些数据包括当前的日期和时间,以及其他系统设置。用户可以通过BIOS(基本输入/输出系统)设置界面访问和修改这些信息。

由于CMOS存储器是非易失性的,即使在断电的情况下也会保持数据,因此计算机可以在重新启动后保持正确的日期和时间,而不需要用户手动重新设置。然而,如果CMOS电池耗尽或者被取下,就会导致CMOS芯片失去电力供应,从而丢失存储的信息,需要重新设置。

2.2、UNIX时间戳

UNIX时间戳是一种用于表示时间的系统,它定义了从UNIX纪元(1970年1月1日00:00:00 UTC)开始经过的秒数,不考虑闰秒。UNIX时间戳通常以整数形式表示,表示从UNIX纪元开始的秒数,可以是正数、零或负数。它被广泛用于计算机系统中,特别是在UNIX和类UNIX操作系统中。

UNIX时间戳的优点是它是一个简单的整数值,易于计算和处理。它在许多系统和编程语言中都被广泛使用,用于记录事件发生的时间、计算时间间隔等。然而,需要注意的是UNIX时间戳并不考虑闰秒,因此在涉及精确时间要求的应用中可能需要进行额外的处理。

2.3、实现时间管理

看一下时间的结构体

struct tm {int tm_sec;    /* 秒 [0, 59] */int tm_min;    /* 分 [0, 59] */int tm_hour;   /* 时 [0, 23] */int tm_mday;   /* 一个月中的天 [1, 31] */int tm_mon;    /* 月份 [0, 11] */int tm_year;   /* 年份 - 1900 */
}__attribute__((packed));struct timespec {uint64_t   tv_sec;  // 秒数uint64_t   tv_msec; // 毫秒数
};

上面的是年月日时分秒,下面的是UNIX时间戳。时间的初始化函数如下

#define BCD_TO_BIN(val) ((val) = ((val) & 15) + ((val) >> 4) * 10)static uint8_t cmos_read(uint8_t addr) {outb(0x70, (0x80 | (addr)));return inb(0x71);
};void time_init(void) {put_str("time_init begin!\n");do {time.tm_sec = cmos_read(0);time.tm_min = cmos_read(2);time.tm_hour = cmos_read(4);time.tm_mday = cmos_read(7);time.tm_mon = cmos_read(8);time.tm_year = cmos_read(9);} while (time.tm_sec != cmos_read(0));BCD_TO_BIN(time.tm_sec);BCD_TO_BIN(time.tm_min);BCD_TO_BIN(time.tm_hour);BCD_TO_BIN(time.tm_mday);BCD_TO_BIN(time.tm_mon);BCD_TO_BIN(time.tm_year);time.tm_year += 100;  // 读取到的年份是24年,unix时间是从1900年开始算起,所以应该在加100time.tm_mon -= 1;     // 月份从0开始算起times.tv_sec = datetime_to_timestamp(&time);times.tv_msec = times.tv_sec;put_str("time_init end!\n");
}

BCD_TO_BIN宏的作用是将数据从格雷码变为十进制。cmos_read的作用是读取CMOS的地址,从而读出年月日。

我们返回unix时间戳的代码就很简单了。

/* 获取当前的 unix 时间戳 */
uint64_t get_time(void) {return mod_u64_u32(ticks, 100) + times.tv_sec;
}

这个时间戳也就是现在的时间。在本节还提供两个函数,一个是从UNIX时间戳变为时间的函数,一个是从时间变为UNIX时间戳的代码。

/* 判断是否为闰年 */
static bool is_leap_year(int year) {return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}/* 获取指定年份月份的天数 */
static int days_in_month(int month, int year) {if (month == 2) {return is_leap_year(year) ? 29 : 28;}else if (month == 4 || month == 6 || month == 9 || month == 11) {return 30;}else {return 31;}
}/* 将年月日时分秒转换为 UNIX 时间戳 */
uint64_t datetime_to_timestamp(struct tm* tm) {if (tm == NULL) {return 0; // 空指针检查}uint64_t total_seconds = 0;// 计算年份之前的秒数for (int y = 1970; y < tm->tm_year + 1900; y++) { // tm_year是从1900年开始计算的偏移量total_seconds += is_leap_year(y) ? 31622400 : 31536000; // 闰年秒数 366 * 24 * 60 * 60,平年秒数 365 * 24 * 60 * 60}// 计算月份之前的秒数for (int m = 0; m < tm->tm_mon; m++) { // 月份从0开始total_seconds += days_in_month(m + 1, tm->tm_year + 1900) * 24 * 60 * 60;}// 计算天数之前的秒数total_seconds += (tm->tm_mday - 1) * 24 * 60 * 60;// 计算小时、分钟和秒数total_seconds += tm->tm_hour * 60 * 60 + tm->tm_min * 60 + tm->tm_sec;return total_seconds;
}/* 将 UNIX 时间戳转换为年月日时分秒 */
void timestamp_to_datetime(uint64_t timestamp, struct tm* datetime) {if (datetime == NULL) {return;}// 总天数uint64_t days = divide_u64_u32_no_mod(timestamp, (24 * 3600));int year = 1970;// 计算年份while (days >= 365 + (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))) {days -= 365 + (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));year++;}datetime->tm_year = year;// 计算月份和日期int month = 1;int days_in_mon;while (days >= (days_in_mon = days_in_month(month, year))) {days -= days_in_mon;month++;}datetime->tm_mon = month - 1;datetime->tm_mday = days + 1;// 计算小时、分钟和秒datetime->tm_hour = divide_u64_u32_no_mod(mod_u64_u32(timestamp , (24 * 3600)) , 3600);datetime->tm_min = divide_u64_u32_no_mod(mod_u64_u32(timestamp , (3600)) , 60);datetime->tm_sec = mod_u64_u32(timestamp , 60);
}

这里的代码就只是做计算了,怎么将年月日时分秒映射到时间戳。或者反向映射。

三、持续改进printf

现在的printf无法打印 %ld ,我们把这部分加进去

        case 'l':index_char = *(++index_ptr); // 得到%l后面的字符arg_long = va_arg(ap, uint64_t);if (index_char == 'd') {if (arg_long < 0) {arg_long = 0 - arg_long;*buf_ptr++ = '-'; }itoa64(arg_long, &buf_ptr, 10);}else if (index_char == 'u') {itoa64(arg_long, &buf_ptr, 10);}arg_int = va_arg(ap, int);   // 指针接着向后4位index_char = *(++index_ptr); // 跳过格式字符并更新index_charbreak;}

只添加了对 %l 的处理,其中精髓在于

arg_int = va_arg(ap, int);   // 指针接着向后4位

由于 Uint64_t 是一个64位的结构,所以需要指针 +8 才能指向下一位,但是我们这里在设计一个加8的函数就很复杂,所以直接在处理解除后让指针向后再加4,在下一次遇到 % 处理时也会加4,正好是 +8

为了处理64位无符号数,写了一个新的处理数字的函数

/* 将长整型转换成字符(integer to ascii) */
static void itoa64(uint64_t value, char** buf_ptr_addr, uint8_t base) {uint64_t m=0, i=0;i = divide_u64_u32(value, base, &m);if (i) {                   // 倍数不为0则递归调用itoa64(i, buf_ptr_addr, base);}*((*buf_ptr_addr)++) = cache[m];
}

借助了前面的除法。

仿真

请添加图片描述

结束语

本节我们实现了64位的除法,下节我们将实现文件系统,文件系统较复杂,做好准备。

老规矩,本节的代码地址:https://github.com/lyajpunov/os

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/787986.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

保持ssh断开后,程序不会停止执行

保持ssh断开后&#xff0c;程序不会停止执行 一、前言 笔者做远程部署搞了一阵子&#xff0c;快结项时发现一旦我关闭了ssh连接窗口&#xff0c;远程服务器会自动杀掉我在ssh连接状态下运行的程序。 这怎么行&#xff0c;岂不是想要它一直运行还得要一台电脑一直打开ssh连接咯…

曲线降采样之道格拉斯-普克算法Douglas–Peucker

曲线降采样之道格拉斯-普克算法Douglas–Peucker 该算法的目的是&#xff0c;给定一条由线段构成的曲线&#xff0c;找到一条点数较少的相似曲线&#xff0c;来近似描述原始的曲线&#xff0c;达到降低时间、空间复杂度和平滑曲线的目的。 附赠自动驾驶学习资料和量产经验&…

【C语言】“vid”Microsoft Visual Studio安装及应用(检验内存泄露)

文章目录 前言安装包获取配置VLD完成 前言 我们在写代码时往往容易存在内存泄漏的情况&#xff0c;所以存在这样一个名为VLD的工具用来检验内存泄漏&#xff0c;现在我来教大家安装一下 安装包获取 vld下载网址&#xff1a;https://github.com/KindDragon/vld/releases/tag/…

YOLOv8结合SCI低光照图像增强算法!让夜晚目标无处遁形!【含端到端推理脚本】

这里的"SCI"代表的并不是论文等级,而是论文采用的方法 — “自校准光照学习” ~ 左侧为SCI模型增强后图片的检测效果,右侧为原始v8n检测效果 这篇文章的主要内容是通过使用SCI模型和YOLOv8进行算法联调,最终实现了如上所示的效果:在增强图像可见度的同时,对图像…

【中文视觉语言模型+本地部署 】23.08 阿里Qwen-VL:能对图片理解、定位物体、读取文字的视觉语言模型 (推理最低12G显存+)

项目主页&#xff1a;https://github.com/QwenLM/Qwen-VL 通义前问网页在线使用——&#xff08;文本问答&#xff0c;图片理解&#xff0c;文档解析&#xff09;&#xff1a;https://tongyi.aliyun.com/qianwen/ 论文v3. : 一个全能的视觉语言模型 23.10 Qwen-VL: A Versatile…

读取信息boot.bin和xclbin命令

bootgen读Boot.bin命令 johnjohn-virtual-machine:~/project_zynq/kv260_image_ubuntu22.04$ bootgen -read BOOT-k26-starter-kit-202305_2022.2.bin xclbinutil读xclbin命令 johnjohn-virtual-machine:~/project_zynq/kv260_image_ubuntu22.04$ xclbinutil -i kv260-smartca…

Linux权限提升总结

几个信息收集的项目推荐 运行这几个项目就会在目标主机上收集一些敏感信息供我们参考和使用 一个综合探针&#xff1a;traitor 一个自动化提权&#xff1a;BeRoot(gtfo3bins&lolbas) 使用python2运行beroot.py就可以运行程序&#xff0c;然后就可以收集到系统中的大量信…

mysql锁表问题

问题描述 偶尔应用日志会打印锁表超时回滚 org.springframework.dao.CannotAcquireLockException: ### Error updating database. Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transactionmysql锁…

vue-ueditor-wrap上传图片报错:后端配置项没有正常加载,上传插件不能正常使用

如图所示,今天接收一个项目其中富文本编辑器报错 此项目为vue2项目,富文本编辑器为直接下载好的资源存放在public目录下的 经过排查发现报错的函数在ueditor.all.min.js文件内,但是ueditor.all.min.js文件夹是经过压缩的 所以直接,将index.html中的引用路径修改为ueditor…

商城网站-礼品网站首页html+css+js+说明文档

网页设计与网站建设作业htmlcssjs 预览 说明 单页面&#xff0c;轮播图 获取&#xff1a;https://hpc.baicaitang.cn/2077.html

java的警示之有危险的行为

&#x1f468;‍&#x1f4bb;作者简介&#xff1a;&#x1f468;&#x1f3fb;‍&#x1f393;告别&#xff0c;今天 &#x1f4d4;高质量专栏 &#xff1a;☕java趣味之旅 欢迎&#x1f64f;点赞&#x1f5e3;️评论&#x1f4e5;收藏&#x1f493;关注 &#x1f496;衷心的希…

06 | Swoole 源码分析之 Coroutine 协程模块

首发原文链接&#xff1a;Swoole 源码分析之 Coroutine 协程模块 大家好&#xff0c;我是码农先森。 引言 协程又称轻量级线程&#xff0c;但与线程不同的是&#xff1b;协程是用户级线程&#xff0c;不需要操作系统参与。由用户显式控制&#xff0c;可以在需要的时候挂起、或…

Redis中的复制功能(三)

复制 服务器运行ID 除了复制偏移量和复制积压缓冲区之外&#xff0c;实现部分重同步还需要用到服务器运行ID(run ID): 1.每隔Redis服务器&#xff0c;不论主服务器还是从服务&#xff0c;都会有自己的运行ID2.运行ID在服务器启动时自动生成&#xff0c;由40个随机的十六进制…

迈向数字化医疗:互联网医院APP开发中的设计思路与技术要点

在开发互联网医院APP时&#xff0c;需要综合考虑设计思路和技术要点&#xff0c;确保用户体验和医疗服务质量的提升。接下来&#xff0c;小编将从设计思路和技术要点两个方面进行讲解。 一、设计思路 用户导向&#xff1a;在设计互联网医院APP时&#xff0c;需要将用户体验放在…

RocketMQ 消费者源码解读:消费过程、负载原理、顺序消费原理

B站学习地址 上一遍学习了三种常见队列的消费原理&#xff0c;本次我们来从源码的角度来证明上篇中的理论。 1、准备 RocketMQ 版本 <!-- RocketMQ --> <dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-spring-boot-s…

vs2022断点找bug出错(打上100个断点)

初步分析&#xff1a;故障出自-具体功能模块 进一步分析&#xff1a;故障出自-该功能代码流程 进一步分析&#xff1a;从该功能起点-终点&#xff0c;一路打100个断点

ICLR 2024 | 鸡生蛋蛋生鸡?再论生成数据能否帮助模型训练

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 新建了人工智能中文站https://ai.weoknow.com 每天给大家更新可用的国内可用chatGPT资源 发布在https://it.weoknow.com 更多资源欢迎关注 随着生成模型&#xff08;如 ChatGPT、扩散模型&#xff09;飞速发展&#x…

Nomad Web更新没有最快只有更快

大家好&#xff0c;才是真的好。 很长时间没介绍运行在浏览器中的Notes客户端即Nomad Web更新情况。 不用安装&#xff0c;直接使用&#xff0c;还可以完美地兼容适应各种操作系统&#xff0c;Nomad Web一定是Notes/Domino产品现在和将来重点发展的用户访问模式。 不过&…

【CKA模拟题】一文教你用StorageClass轻松创建PV

题干 For this question, please set this context (In exam, diff cluster name) kubectl config use-context kubernetes-adminkubernetesYour task involves setting up storage components in a Kubernetes cluster. Follow these steps: Step 1: Create a Storage Class…

书生 浦语 大模型趣味 Demo

目录 一. 部署 InternLM2-Chat-1.8B 模型进行智能对话 1. 环境准备 2. 下载模型参数 3. 运行Demo 二. 部署实战营 八戒-Chat-1.8B 模型 1. 下载Demo仓库 2. 启动web服务端加载八戒模型&#xff1a; 3. 将SSH远程端口映射到本地 4. 在本地浏览器打开&#xff1a;http:/…