【翻译WINDOWS NT FILE SYSTEM INTERNAL】NT缓存管理器一(7)

缓存管理器接口

       现在我们探讨了一般情况下文件系统驱动怎么使用缓存,让我们系统组件使用NT缓存管理器的不同方式。文件系统驱动和WINDOWS NT操作系统中其他组件可以通过四组接口例程来使用缓存管理器提供的服务。第一组接口历程提供对文件流访问和操作的支持,但是其他三组可以用作不同系统缓存访问方法。

       NT缓存管理器提供四组接口是文件流操作函数,拷贝接口,MDL接口,锁定接口(pinning interface)。

 

文件流操作函数

       缓存管理器提供对初始化文件流缓存,终止缓存,(在需要时)回写缓存到磁盘,修改文件大小,清除缓存数据,清零文件数据的支持,还支持还支持文件系统日志,以及一些其他的常用的维护性功能。缓存管理器这个接口集包括以下函数:

1.      CcInitializeCacheMap

2.      CcUninitializeCacheMap

3.      CcSetFileSizes

4.      CcPurgeCacheSection

5.      CcSetDirtyPageThreshold

6.      CcFlushCache

7.      CcZeroData

8.      CcGetFileObjectFromSectionPtrs

9.      CcSetLogHandleForFile

10.  CcSetAdditionalCacheAttributes

11.  CcGetDirtyPages

12.  CcIsThereDirtyData

13.  CcGetLsnForFileObject

 

拷贝接口

       拷贝接口是最简单的缓存访问形式。客户端模型,使用缓存管理器,可以使用这些接口来从一个内存缓冲区缓冲区中拷贝一个字节范围到文件流缓存中指定的虚拟字节偏移,或者(一个字节范围)从文件流缓存中一个指定的虚拟字节偏移复制到一个内存缓冲区.

       接口包括一个初始化预读(read-ahead)的调用,和一些支持write throttling的调用。如果系统运行在低可用或可修改页数的情况下(我理解意思是可用内存或可修改内存少的时候) ,write throttling允许缓存管理器客户端(一般是一个文件系统驱动)延迟真正的写操作。如果一些应用程序极端频繁的修改数据,大于 延迟写者和修改页写者可以初始化传输修改数据到磁盘或者通过网络到一个存储服务器 的频率(greater than the rate

at which the lazy writer ormodified page writer can initiate the transfer of modified

data to disk oracross the network to a storage server),这个条件可以发生。注意,这是完全可能的,磁盘或者网络驱动可能没有能力与修改页写者(the modified page writer)或者延迟写者(lazy writer)正在生成的写数据的I/O请求的速度并驾齐驱(keep pace with sth)。这可能会导致未修改页可用数量降低。

       缓存管理器这个接口集合包括以下例程:

1.      CcCopyRead/CcFastCopyRead

2.      CcCopyWrite/CcFastCopyWrite

3.      CcCanlWrite

4.      CcDeferWrite

5.      CcSetReadAheadGranularity

6.      CcScheduleReadAhead

 

MDL接口

       一个内存描述列表(MDL)是一个不透明的映射实际虚拟地址范围到一个或多个基于页的物理地址范围的内存管理定义的数据接口。对于缓存管理器,MDL接口允许通过DMA(Direct Memory Access)直接访问系统缓存。这个例程集合组成MDL接口返回一个MDL给调用者,这个MDL包括请求中描述的字节范围,这个字节范围可以接连被调用者用来直接传输数据进或出系统缓存。

       这个接口对于需要直接访问系统缓存内容的子系统是很有用的。例如,需要使用DMA通过网络设备直接进入或取出缓存管理器虚拟地址范围的网络文件服务,使用MDL接口来实现高性能。当这个接口不在的时候(in the absence of this interface),从系统缓存向外传送数据的网络驱动程序第一次可能会分配一个临时缓冲区,用来从系统缓存拷贝数据到这个临时缓冲区,让网络设备执行传输,最后释放临时缓冲区。如果数据可以直接被网络设备通过网络从系统缓存向外传输,分配释放临时缓冲区的额外调用以及冗余的拷贝可以全部被避免。通过使用CcMdlRead和CcMdlReadComplete顺序调用可以实现这个目的,是确定无疑的。

       注意,像拷贝接口一样,这些接口共享相同的预读调用。MDL接口和那些属于拷贝接口的例程可以被同时用在相同的文件流。缓存管理器这个例程集提供的例程:

1.      CcMdlRead

2.      CcMdlReadComplete

3.      CcPrepareMdlWrite

4.      CcMdlWriteComplete

值得一提的一个有兴趣的点是,当缓存管理器中大多数其他关于文件数据传输的例程(例如CcMdlRead,CcCopyRead)执行接口自身提供的数据传输功能时,CcPrepareMdlWrite()例程简单的创建一个包含原始数据的MDL,原始数据可以被之后的优先调用者修改,通过调用CcMdlWriteComplete()。因此,尽管在CcPrepareMdlWrite()被调用的时候,一些数据传输可能被缓存管理器执(为了从磁盘或者通过网络和MDL描述的页内存中获取当前文件流数据),但这个例程更像是一个使能器例程,允许调用者使用返回的MDL延迟传输数据。

 

锁定接口

       缓存管理器提供的这个接口可以被用来执行两种任务:

1.      为了使用缓冲区指针直接访问数据,映射数据到系统缓存。

2.      锁住支持映射数据的物理页

另外,可以使用缓冲区指针直接读数据,调用者可以仅仅在系统缓存里直接修改数据。

当被映射数据不再需要被访问时,数据可以被解锁。这将仅仅导致被锁定的页正在解锁,并对其他可用。一旦数据被解锁,数据指针不能被继续使用。锁定数据一般用来提供效率,当文件系统驱动或者其他系统组件需要使用内存里的数据结构频繁直接访问时。这个仅仅用来保证,正在被访问的数据不能从系统内存中移除。无论如何,锁定被映射数据消耗物理内存,因此会减少其他系统组件大量可用内存。

       注意,锁定接口不能直接与拷贝接口或者MDL接口一起使用。

       在操作被缓存的文件系统元数据的时候,这个接口经常被文件系统驱动用来处理被缓存的文件系统元数据。锁定接口由以下例程组成:

1.      CcMapData

2.      CcPinMappedData

3.      CcPinRead

4.      CcSetDirtyPinnedData

5.      CcPreparePinWrite

6.      CcUnpinData

7.      CcUnpinDataForThread

8.      CcRepinBcb

9.      CcUnpinRepinnedBcb

10.  CcGetFileObjectFromBcb

上面的函数将在第七章,NT缓存管理器二,中详细描述。

import json import requests import os import time from datetime import datetime, timedelta import pytz from mutagen.mp3 import MP3 from mutagen.mp4 import MP4 import subprocess import platform # 全局变量,用于缓存 tenant_access_token 和上次更新的时间 tenant_access_token = None token_last_updated = None def get_broadcast_data(): """ 获取并提取播报数据 """ # 获取 tenant_access_token global tenant_access_token, token_last_updated # 检查 token 是否过期,过期则重新获取 if tenant_access_token is None or token_last_updated is None or (datetime.now() - token_last_updated) > timedelta(hours=2): tenant_access_token = get_auth_token() if tenant_access_token: token_last_updated = datetime.now() if not tenant_access_token: print("获取 tenant_access_token 失败!") return [] # 获取 Feishu Bitable 数据 url = 'https://open.feishu.cn/open-apis/bitable/v1/apps/E1zybPqiqa0TaesZjKKch5ZcnJd/tables/tblwFY4k3pmrV5WK/records/search' headers = { 'Content-Type': 'application/json', 'Authorization': f'Bearer {tenant_access_token}' # 使用获取到的 token } data = {} # 如果需要传递查询条件,可以在这里添加 try: response = requests.post(url, headers=headers, json=data) response.raise_for_status() # 如果响应失败,将抛出异常 response_dict = response.json() # 将返回的 JSON 数据转换为字典 items = response_dict.get("data", {}).get("items", []) data = [] for item in items: fields = item.get("fields", {}) data.append({ "播音日期": extract_broadcast_date(fields, '播音日期'), "时间段": extract_time_segment(fields, '时间段'), "开播音乐file_token": extract_file_token(fields, '开播音乐'), "开场白-播报file_token": extract_file_token(fields, '开场白-播报'), "需更新文案-播报file_token": extract_file_token(fields, '需更新文案-播报'), "壹首歌file_token": extract_file_token(fields, '壹首歌'), "需更新文案2-播报file_token": extract_file_token(fields, '需更新文案2-播报'), "贰首歌file_token": extract_file_token(fields, '贰首歌'), "结束语-播报file_token": extract_file_token(fields, '结束语-播报'), "结束音乐file_token": extract_file_token(fields, '结束音乐') }) return data except requests.exceptions.HTTPError as http_err: print(f"HTTP 错误发生: {http_err}") except Exception as err: print(f"其他错误发生: {err}") return [] def extract_file_token(fields, field_name): """提取 file_token""" field_data = fields.get(field_name, []) if isinstance(field_data, list) and len(field_data) > 0: value = field_data[0] if isinstance(value, dict): return value.get("file_token", "") return '' def extract_time_segment(fields, field_name): """提取时间段字段""" field_data = fields.get(field_name, []) if isinstance(field_data, list) and len(field_data) > 0: value = field_data[0] if isinstance(value, dict): return value.get("text", "") return None def extract_broadcast_date(fields, field_name): """提取播音日期字段""" field_data = fields.get(field_name, 0) if isinstance(field_data, int): try: timestamp = field_data / 1000 # 时间戳转化为秒 parsed_date = datetime.fromtimestamp(timestamp, tz=pytz.utc).astimezone(pytz.timezone('Asia/Shanghai')) return parsed_date.strftime("%Y-%m-%d") # 转换为 "YYYY-MM-DD" 格式 except (ValueError, OverflowError): pass return None def get_auth_token(): """获取认证 token""" url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal" headers = {"Content-Type": "application/json; charset=utf-8"} payload = {"app_id": "cli_a882683e8779d00c", "app_secret": "3NKkALA7vyMRVnpKJinmrb1LJ7YuK4H0"} try: response = requests.post(url, json=payload, headers=headers) response.raise_for_status() data = response.json() if data["code"] == 0: return data["tenant_access_token"] else: print(f"请求失败:{data['msg']}(错误码:{data['code']})") except requests.exceptions.RequestException as e: print(f"请求异常:{e}") return None def create_folder(folder_name): """创建文件夹""" if not os.path.exists(folder_name): os.makedirs(folder_name) def download_file(file_token, save_path, authorization): """下载文件""" url = f"https://open.feishu.cn/open-apis/drive/v1/medias/{file_token}/download" headers = {"Authorization": "Bearer " + authorization} try: response = requests.get(url, headers=headers, stream=True) if response.status_code == 200: with open(save_path, 'wb') as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) print(f"文件已成功下载到: {save_path}") return True else: print(f"请求失败,状态码: {response.status_code}") print(f"错误信息: {response.text}") except Exception as e: print(f"发生异常: {str(e)}") return False def get_audio_duration(file_path): """获取音频时长""" try: if file_path.endswith(".mp3"): audio = MP3(file_path) elif file_path.endswith(".mp4"): audio = MP4(file_path) else: print(f"不支持的文件格式: {file_path}") return 0 return audio.info.length except Exception as e: print(f"获取音频时长失败: {e}") return 0 def kill_previous_players(): """清理之前残留的播放器进程""" system = platform.system() # 获取当前操作系统类型 try: if system == "Windows": subprocess.run(['taskkill', '/F', '/IM', 'wmplayer.exe'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) subprocess.run(['taskkill', '/F', '/IM', 'vlc.exe'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) elif system == "Darwin": # macOS subprocess.run(['killall', 'afplay'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) elif system == "Linux": subprocess.run(['pkill', 'mpg123'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) print("已清理之前残留的播放器进程") except Exception as e: print(f"清理播放器进程时发生错误: {e}") def play_music_in_folder(folder_path): """播放文件夹中的音频文件,并在播放完成后关闭播放器""" audio_files = [f for f in os.listdir(folder_path) if f.endswith((".mp3", ".mp4"))] processes = [] # 用于存储播放器进程 for file_name in audio_files: full_file_path = os.path.join(folder_path, file_name) try: duration = get_audio_duration(full_file_path) if duration <= 0: print(f"无法获取 {file_name} 的时长,跳过播放") continue print(f"播放 {file_name},预计播放时长:{duration} 秒") if os.name == 'nt': # Windows 系统 process = subprocess.Popen(['start', '', full_file_path], shell=True) elif os.name == 'posix': # MacOS 或 Linux 系统 process = subprocess.Popen(['afplay', full_file_path]) # 对于 MacOS 使用 afplay else: print(f"不支持的操作系统类型: {os.name}") continue processes.append(process) # 保存进程对象 time.sleep(duration) # 等待音频播放完成 except Exception as e: print(f"无法播放 {full_file_path}: {e}") # 关闭所有播放器进程 for process in processes: try: if process.poll() is None: # 检查进程是否仍在运行 process.kill() # 强制终止进程 print("播放器已强制关闭") except Exception as e: print(f"关闭播放器失败: {e}") def process_time_segment(segment, download_offset, play_offset, data, authorization, folder_name): """处理个时间段的下载和播放""" target_data = next((entry for entry in data if entry["时间段"] == segment), None) if not target_data: print(f"未找到时间段 {segment} 的文件数据!") return current_time = datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%H:%M') segment_start_time = segment.split("-")[0] # 如果当前时间已经超过该时间段的开始时间,则跳过 if current_time > segment_start_time: print(f"当前时间已超过时间段 {segment} 的开始时间,跳过该时间段的处理") return # 等待到达下载时间 download_time = (datetime.strptime(segment_start_time, "%H:%M") - timedelta(minutes=download_offset)).strftime("%H:%M") while current_time != download_time: current_time = datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%H:%M') time.sleep(1) print(f"开始下载 {segment} 的文件") files = [] file_tokens = [ ("开播音乐file_token", "mp3"), ("开场白-播报file_token", "mp4"), ("需更新文案-播报file_token", "mp4"), ("壹首歌file_token", "mp3"), ("需更新文案2-播报file_token", "mp4"), ("贰首歌file_token", "mp3"), ("结束语-播报file_token", "mp4"), ("结束音乐file_token", "mp3") ] for i, (key, file_format) in enumerate(file_tokens): token = target_data.get(key) if token: save_path = os.path.join(folder_name, f"file_{i+1}.{file_format}") if download_file(token, save_path, authorization): files.append(save_path) # 清理之前残留的播放器进程(调整到这里) kill_previous_players() # 等待到达播放时间 play_time = (datetime.strptime(segment_start_time, "%H:%M") - timedelta(minutes=play_offset)).strftime("%H:%M") while current_time != play_time: current_time = datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%H:%M') time.sleep(1) print(f"开始播放 {segment} 的文件") play_music_in_folder(folder_name) # 播放结束后再次清理播放器进程 kill_previous_players() # 删除下载的文件 for file in files: os.remove(file) print(f"已删除文件: {file}") def main(): """主函数""" data = get_broadcast_data() if not data: print("未获取到有效的数据!") return global tenant_access_token authorization = tenant_access_token if not authorization: return folder_name = "bobao" create_folder(folder_name) segments = [ ("08:10-08:15", 10, 0), # 提前10分钟下载文件,0-准点播放 ("10:30-10:40", 10, 0), ("13:00-13:10", 10, 0), ("15:00-15:10", 10, 0), ("16:30-16:35", 10, 0), ("19:00-19:14", 10, 0) ] for segment, download_offset, play_offset in segments: process_time_segment(segment, download_offset, play_offset, data, authorization, folder_name) # 主程序结束后,再清理次播放器进程 kill_previous_players() if __name__ == "__main__": main() 上述代码会不会出现请求频率过高的问题?
05-28
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值