diff --git a/kernel/src/debug/klog/loglevel.rs b/kernel/src/debug/klog/loglevel.rs new file mode 100644 index 000000000..e35343d95 --- /dev/null +++ b/kernel/src/debug/klog/loglevel.rs @@ -0,0 +1,193 @@ +//! 内核日志级别管理模块 +//! +//! 提供类似Linux内核的loglevel功能,支持通过cmdline参数和procfs接口 +//! 动态控制内核日志输出级别。 + +use core::sync::atomic::{AtomicU8, Ordering}; + +use system_error::SystemError; + +use crate::init::cmdline::{KernelCmdlineKV, KernelCmdlineParameter, KCMDLINE_PARAM_KV}; + +/// 全局内核日志级别配置 +/// +/// 遵循Linux内核语义: +/// - console_loglevel: 控制台输出级别,只有级别小于等于此值的消息才会输出 +/// - default_message_loglevel: 默认消息级别 +/// - minimum_console_loglevel: 最小控制台级别 +/// - default_console_loglevel: 默认控制台级别 +pub static KERNEL_LOG_LEVEL: KernelLogLevel = KernelLogLevel::new(); + +/// 日志级别 +#[derive(Default, Clone, PartialEq, Debug)] +pub enum LogLevel { + EMERG = 0, + ALERT = 1, + CRIT = 2, + ERR = 3, + WARN = 4, + NOTICE = 5, + INFO = 6, + DEBUG = 7, + #[default] + DEFAULT = 8, +} + +impl From for LogLevel { + fn from(value: usize) -> Self { + match value { + 0 => LogLevel::EMERG, + 1 => LogLevel::ALERT, + 2 => LogLevel::CRIT, + 3 => LogLevel::ERR, + 4 => LogLevel::WARN, + 5 => LogLevel::NOTICE, + 6 => LogLevel::INFO, + 7 => LogLevel::DEBUG, + _ => LogLevel::DEFAULT, + } + } +} + +impl From<::log::Level> for LogLevel { + fn from(value: ::log::Level) -> Self { + match value { + log::Level::Error => LogLevel::ERR, // 3 + log::Level::Warn => LogLevel::WARN, // 4 + log::Level::Info => LogLevel::INFO, // 6 + log::Level::Debug => LogLevel::DEBUG, // 7 + log::Level::Trace => LogLevel::DEBUG, // 7 (maps to DEBUG) + } + } +} + +/// 内核日志级别管理结构 +#[derive(Debug)] +pub struct KernelLogLevel { + /// 控制台输出级别 (console_loglevel) + /// 只有级别小于等于此值的消息才会输出到控制台 + pub console_level: AtomicU8, + /// 默认消息级别 (default_message_loglevel) + pub default_message_level: AtomicU8, + /// 最小控制台级别 (minimum_console_loglevel) + pub minimum_level: AtomicU8, + /// 默认控制台级别 (default_console_loglevel) + pub default_console_level: AtomicU8, +} + +impl KernelLogLevel { + /// 创建新的内核日志级别配置 + /// + /// 默认值遵循Linux内核标准: + /// - console_loglevel: 7 (DEBUG) + /// - default_message_loglevel: 4 (WARNING) + /// - minimum_console_loglevel: 1 (ALERT) + /// - default_console_loglevel: 7 (DEBUG) + pub const fn new() -> Self { + Self { + console_level: AtomicU8::new(7), // DEBUG级别 + default_message_level: AtomicU8::new(4), // WARNING级别 + minimum_level: AtomicU8::new(1), // ALERT级别 + default_console_level: AtomicU8::new(7), // DEBUG级别 + } + } + + /// 检查消息是否应该被输出到控制台 + /// + /// # 参数 + /// - `message_level`: 消息级别 (0-7) + /// + /// # 返回值 + /// - `true`: 消息级别 <= 控制台级别,应该输出 + /// - `false`: 消息级别 > 控制台级别,应该过滤 + /// + /// # Linux语义 + /// 遵循Linux内核的过滤规则:message_level <= console_loglevel + pub fn should_print(&self, message_level: LogLevel) -> bool { + let console_level = self.console_level.load(Ordering::Acquire); + // Linux语义:message_level <= console_loglevel 时输出 + message_level as u8 <= console_level + } + + /// 设置控制台日志级别 + /// + /// # 参数 + /// - `level`: 新的控制台级别 (0-7) + /// + /// # 返回值 + /// - `Ok(())`: 设置成功 + /// - `Err(SystemError::EINVAL)`: 级别值无效 + pub fn set_console_level(&self, level: u8) -> Result<(), SystemError> { + if level <= 7 { + self.console_level.store(level, Ordering::Release); + Ok(()) + } else { + Err(SystemError::EINVAL) + } + } + + /// 获取当前控制台日志级别 + pub fn get_console_level(&self) -> u8 { + self.console_level.load(Ordering::Acquire) + } + + /// 获取默认消息级别 + pub fn get_default_message_level(&self) -> u8 { + self.default_message_level.load(Ordering::Acquire) + } + + /// 获取最小控制台级别 + pub fn get_minimum_level(&self) -> u8 { + self.minimum_level.load(Ordering::Acquire) + } + + /// 获取默认控制台级别 + pub fn get_default_console_level(&self) -> u8 { + self.default_console_level.load(Ordering::Acquire) + } + + /// 获取所有日志级别配置 + /// + /// # 返回值 + /// 返回包含四个级别值的数组:[console, default_message, minimum, default_console] + pub fn get_all_levels(&self) -> [u8; 4] { + [ + self.get_console_level(), + self.get_default_message_level(), + self.get_minimum_level(), + self.get_default_console_level(), + ] + } +} + +/// loglevel命令行参数处理 +/// +/// 支持格式:loglevel=N (N=0-7) +/// 示例:loglevel=4 只输出WARNING及以上级别的日志 +#[linkme::distributed_slice(KCMDLINE_PARAM_KV)] +static LOGLEVEL_PARAM: KernelCmdlineParameter = KernelCmdlineParameter::KV(KernelCmdlineKV { + name: "loglevel", + value: None, + initialized: false, + default: "7", // 默认DEBUG级别 +}); + +/// 处理loglevel参数 +/// +/// 在cmdline参数解析完成后调用,设置全局日志级别 +pub fn handle_loglevel_param() { + if let Some(param) = KCMDLINE_PARAM_KV.iter().find(|x| x.name() == "loglevel") { + if let Some(value_str) = param.value_str() { + if let Ok(level) = value_str.parse::() { + if level <= 7 { + let _ = KERNEL_LOG_LEVEL.set_console_level(level); + log::info!("loglevel: set console log level to {} via cmdline", level); + } else { + log::warn!("loglevel: invalid level {}, must be 0-7", level); + } + } else { + log::warn!("loglevel: invalid value '{}', must be a number", value_str); + } + } + } +} diff --git a/kernel/src/debug/klog/mod.rs b/kernel/src/debug/klog/mod.rs index 6d106477f..21b40947e 100644 --- a/kernel/src/debug/klog/mod.rs +++ b/kernel/src/debug/klog/mod.rs @@ -1 +1,2 @@ +pub mod loglevel; pub mod mm; diff --git a/kernel/src/filesystem/procfs/log.rs b/kernel/src/filesystem/procfs/klog.rs similarity index 56% rename from kernel/src/filesystem/procfs/log.rs rename to kernel/src/filesystem/procfs/klog.rs index 01771effe..9de424e38 100644 --- a/kernel/src/filesystem/procfs/log.rs +++ b/kernel/src/filesystem/procfs/klog.rs @@ -2,57 +2,7 @@ use core::fmt::{Display, Formatter, Result}; use alloc::string::String; -use crate::time::PosixTimeSpec; - -// /// 日志类型 -// #[derive(Default, Clone, Debug)] -// pub enum LogType { -// /// 启动信息 -// Startup, -// /// 驱动信息 -// Driver, -// /// 系统信息 -// System, -// /// 硬件信息 -// Hardware, -// /// 内核模块信息 -// KernelModule, -// /// 内核调试信息 -// KernelDebug, -// #[default] -// Default, -// } - -/// 日志级别 -#[derive(Default, Clone, PartialEq, Debug)] -pub enum LogLevel { - EMERG = 0, - ALERT = 1, - CRIT = 2, - ERR = 3, - WARN = 4, - NOTICE = 5, - INFO = 6, - DEBUG = 7, - #[default] - DEFAULT = 8, -} - -impl From for LogLevel { - fn from(value: usize) -> Self { - match value { - 0 => LogLevel::EMERG, - 1 => LogLevel::ALERT, - 2 => LogLevel::CRIT, - 3 => LogLevel::ERR, - 4 => LogLevel::WARN, - 5 => LogLevel::NOTICE, - 6 => LogLevel::INFO, - 7 => LogLevel::DEBUG, - _ => LogLevel::DEFAULT, - } - } -} +use crate::{debug::klog::loglevel::LogLevel, time::PosixTimeSpec}; /// 日志消息 #[derive(Default, Clone, Debug)] diff --git a/kernel/src/filesystem/procfs/kmsg.rs b/kernel/src/filesystem/procfs/kmsg.rs index 383fa7315..01d11e300 100644 --- a/kernel/src/filesystem/procfs/kmsg.rs +++ b/kernel/src/filesystem/procfs/kmsg.rs @@ -1,8 +1,8 @@ use core::sync::atomic::{compiler_fence, Ordering}; -use super::log::{LogLevel, LogMessage}; +use super::klog::LogMessage; -use crate::libs::spinlock::SpinLock; +use crate::{debug::klog::loglevel::LogLevel, libs::spinlock::SpinLock}; use alloc::{borrow::ToOwned, string::ToString, vec::Vec}; @@ -52,7 +52,7 @@ impl Kmsg { /// 添加日志消息 pub fn push(&mut self, msg: LogMessage) { - self.buffer.push(msg); + self.buffer.push(msg.clone()); self.is_changed = true; } diff --git a/kernel/src/filesystem/procfs/mod.rs b/kernel/src/filesystem/procfs/mod.rs index 541f207b7..e85aa54c9 100644 --- a/kernel/src/filesystem/procfs/mod.rs +++ b/kernel/src/filesystem/procfs/mod.rs @@ -15,9 +15,12 @@ use crate::{ arch::mm::LockedFrameAllocator, driver::base::device::device_number::DeviceNumber, filesystem::{ - procfs::proc_thread_self_ns::{ - current_thread_self_ns_ino, open_thread_self_ns_file, read_thread_self_ns_link, - ThreadSelfNsFileType, + procfs::{ + proc_thread_self_ns::{ + current_thread_self_ns_ino, open_thread_self_ns_file, read_thread_self_ns_link, + ThreadSelfNsFileType, + }, + sys::sysctl::PrintkSysctl, }, vfs::{ mount::{MountFlags, MountPath}, @@ -43,13 +46,14 @@ use super::vfs::{ FileSystem, FsInfo, IndexNode, InodeId, Magic, Metadata, SuperBlock, }; +pub mod klog; pub mod kmsg; -pub mod log; mod proc_cpuinfo; mod proc_mounts; mod proc_thread_self_ns; mod proc_version; mod procfs_setup; +mod sys; mod syscall; /// @brief 进程文件类型 @@ -77,6 +81,8 @@ pub enum ProcFileType { ProcThreadSelfNsRoot, /// /proc/thread-self/ns/* namespace files ProcThreadSelfNsChild(ThreadSelfNsFileType), + /// /proc/sys/kernel/printk + ProcSysKernelPrintk, //todo: 其他文件类型 ///默认文件类型 Default, @@ -799,7 +805,8 @@ impl IndexNode for LockedProcFSInode { ProcFileType::ProcKmsg | ProcFileType::ProcFdDir | ProcFileType::ProcFdFile - | ProcFileType::ProcThreadSelfNsRoot => 0, + | ProcFileType::ProcThreadSelfNsRoot + | ProcFileType::ProcSysKernelPrintk => 0, }; // 为不同类型的 procfs 节点设置文件私有数据 @@ -883,6 +890,12 @@ impl IndexNode for LockedProcFSInode { return read_thread_self_ns_link(ns_type, buf, offset) } + ProcFileType::ProcSysKernelPrintk => { + // 使用 PrintkSysctl 处理 /proc/sys/kernel/printk 文件读取 + let buflen = buf.len().min(len); + return PrintkSysctl.read_to_buffer(&mut buf[..buflen]); + } + _ => (), }; @@ -903,12 +916,32 @@ impl IndexNode for LockedProcFSInode { fn write_at( &self, - _offset: usize, - _len: usize, - _buf: &[u8], + offset: usize, + len: usize, + buf: &[u8], _data: SpinLockGuard, ) -> Result { - return Err(SystemError::ENOSYS); + if offset > 0 { + // 不支持随机写入 + return Err(SystemError::EINVAL); + } + + let inode = self.0.lock(); + + // 检查当前inode是否为一个文件夹,如果是的话,就返回错误 + if inode.metadata.file_type == FileType::Dir { + return Err(SystemError::EISDIR); + } + + // 根据文件类型处理写入 + match inode.fdata.ftype { + ProcFileType::ProcSysKernelPrintk => { + // 使用 PrintkSysctl 处理 /proc/sys/kernel/printk 文件写入 + let buflen = buf.len().min(len); + return PrintkSysctl.write_from_buffer(&buf[..buflen]); + } + _ => Err(SystemError::ENOSYS), + } } fn fs(&self) -> Arc { diff --git a/kernel/src/filesystem/procfs/procfs_setup.rs b/kernel/src/filesystem/procfs/procfs_setup.rs index cd029a6d9..bfc440933 100644 --- a/kernel/src/filesystem/procfs/procfs_setup.rs +++ b/kernel/src/filesystem/procfs/procfs_setup.rs @@ -13,6 +13,7 @@ impl ProcFS { self.create_version_file(); self.create_cpuinfo_file(); self.create_self_file(); + self.create_sysctl_files(); } /// @brief 创建 /proc/meminfo 文件 diff --git a/kernel/src/filesystem/procfs/sys/mod.rs b/kernel/src/filesystem/procfs/sys/mod.rs new file mode 100644 index 000000000..2b3e34d96 --- /dev/null +++ b/kernel/src/filesystem/procfs/sys/mod.rs @@ -0,0 +1,35 @@ +use crate::filesystem::{ + procfs::{ProcFS, ProcFileCreationParams, ProcFileType}, + vfs::{syscall::ModeType, FileSystem, FileType}, +}; + +pub mod sysctl; + +impl ProcFS { + /// @brief 创建 /proc/sys 目录结构及相关文件 + #[inline(never)] + pub(super) fn create_sysctl_files(&self) { + // Create /proc/sys directory + let sys_dir = self + .root_inode() + .create("sys", FileType::Dir, ModeType::from_bits_truncate(0o555)) + .unwrap_or_else(|_| panic!("create /proc/sys error")); + + // Create /proc/sys/kernel directory + let kernel_dir = sys_dir + .create("kernel", FileType::Dir, ModeType::from_bits_truncate(0o555)) + .unwrap_or_else(|_| panic!("create /proc/sys/kernel error")); + + // Create /proc/sys/kernel/printk file + let printk_params = ProcFileCreationParams::builder() + .parent(kernel_dir) + .name("printk") + .file_type(FileType::File) + .mode(ModeType::from_bits_truncate(0o644)) + .ftype(ProcFileType::ProcSysKernelPrintk) + .build() + .unwrap(); + self.create_proc_file(printk_params) + .unwrap_or_else(|_| panic!("create /proc/sys/kernel/printk error")); + } +} diff --git a/kernel/src/filesystem/procfs/sys/sysctl.rs b/kernel/src/filesystem/procfs/sys/sysctl.rs new file mode 100644 index 000000000..80c777818 --- /dev/null +++ b/kernel/src/filesystem/procfs/sys/sysctl.rs @@ -0,0 +1,70 @@ +//! Procfs sysctl接口实现 +//! +//! 提供类似Linux的/proc/sys接口,支持动态配置内核参数 + +use alloc::{string::String, vec::Vec}; +use system_error::SystemError; + +use crate::debug::klog::loglevel::KERNEL_LOG_LEVEL; + +/// 内核日志级别配置管理器 +/// +/// 提供读取和写入内核日志级别配置的功能 +/// 格式:console_loglevel\tdefault_message_loglevel\tminimum_console_loglevel\tdefault_console_loglevel +#[derive(Debug)] +pub struct PrintkSysctl; + +impl PrintkSysctl { + /// 读取当前的内核日志级别配置 + /// + /// 返回格式:"console_loglevel\tdefault_message_loglevel\tminimum_console_loglevel\tdefault_console_loglevel\n" + fn read_config(&self) -> Result { + let levels = KERNEL_LOG_LEVEL.get_all_levels(); + Ok(format!( + "{}\t{}\t{}\t{}\n", + levels[0], levels[1], levels[2], levels[3] + )) + } + + /// 写入内核日志级别配置 + /// + /// 支持写入单个值或多个值,但只处理第一个值(控制台日志级别) + /// 输入数据格式:"console_loglevel" 或 "console_loglevel default_message_loglevel minimum_console_loglevel default_console_loglevel" + fn write_config(&self, data: &[u8]) -> Result { + let input = core::str::from_utf8(data).map_err(|_| SystemError::EINVAL)?; + let parts: Vec<&str> = input.split_whitespace().collect(); + + if parts.is_empty() { + return Err(SystemError::EINVAL); + } + + // 只处理第一个值(控制台日志级别) + if let Ok(level) = parts[0].parse::() { + KERNEL_LOG_LEVEL.set_console_level(level)?; + log::info!( + "sysctl: set console log level to {} via /proc/sys/kernel/printk", + level + ); + Ok(data.len()) + } else { + log::warn!("sysctl: invalid loglevel value '{}'", parts[0]); + Err(SystemError::EINVAL) + } + } + + /// 读取配置到缓冲区(用于文件系统读取操作) + pub fn read_to_buffer(&self, buf: &mut [u8]) -> Result { + let content = self.read_config()?; + let content_bytes = content.as_bytes(); + + let read_len = buf.len().min(content_bytes.len()); + buf[..read_len].copy_from_slice(&content_bytes[..read_len]); + + Ok(read_len) + } + + /// 从缓冲区写入配置(用于文件系统写入操作) + pub fn write_from_buffer(&self, buf: &[u8]) -> Result { + self.write_config(buf) + } +} diff --git a/kernel/src/init/cmdline.rs b/kernel/src/init/cmdline.rs index c8a04cfc9..100ff33bd 100644 --- a/kernel/src/init/cmdline.rs +++ b/kernel/src/init/cmdline.rs @@ -182,19 +182,19 @@ impl KernelCmdlineArg { } pub struct KernelCmdlineKV { - name: &'static str, - value: Option, - initialized: bool, - default: &'static str, + pub name: &'static str, + pub value: Option, + pub initialized: bool, + pub default: &'static str, } /// 在内存管理初始化之前的KV参数 pub struct KernelCmdlineEarlyKV { - name: &'static str, - value: [u8; Self::VALUE_MAX_LEN], - index: usize, - initialized: bool, - default: &'static str, + pub name: &'static str, + pub value: [u8; Self::VALUE_MAX_LEN], + pub index: usize, + pub initialized: bool, + pub default: &'static str, } #[allow(dead_code)] @@ -383,6 +383,8 @@ impl KernelCmdlineManager { fence(Ordering::SeqCst); // 初始化默认值 self.default_initialize(); + // 处理loglevel参数 + crate::debug::klog::loglevel::handle_loglevel_param(); fence(Ordering::SeqCst); } diff --git a/kernel/src/init/init.rs b/kernel/src/init/init.rs index 6b356c0da..965b72c3f 100644 --- a/kernel/src/init/init.rs +++ b/kernel/src/init/init.rs @@ -69,6 +69,7 @@ fn do_start_kernel() { } // 初始化内核命令行参数 kenrel_cmdline_param_manager().init(); + boot_callback_except_early(); init_intertrait(); diff --git a/kernel/src/libs/printk.rs b/kernel/src/libs/printk.rs index 7ba026023..91af805cc 100644 --- a/kernel/src/libs/printk.rs +++ b/kernel/src/libs/printk.rs @@ -6,11 +6,9 @@ use log::{info, Level, Log}; use super::lib_ui::textui::{textui_putstr, FontColor}; use crate::{ + debug::klog::loglevel::{LogLevel, KERNEL_LOG_LEVEL}, driver::tty::{tty_driver::TtyOperation, virtual_terminal::vc_manager}, - filesystem::procfs::{ - kmsg::KMSG, - log::{LogLevel, LogMessage}, - }, + filesystem::procfs::{klog::LogMessage, kmsg::KMSG}, time::PosixTimeSpec, }; @@ -69,10 +67,9 @@ pub fn __printk(args: fmt::Arguments) { pub struct Logger; impl Logger { - pub fn log(&self, log_level: usize, message: fmt::Arguments) { + pub fn log(&self, log_level: LogLevel, message: fmt::Arguments) { if unsafe { KMSG.is_some() } { let timestamp: PosixTimeSpec = PosixTimeSpec::now_cpu_time(); - let log_level = LogLevel::from(log_level); let log_message = LogMessage::new(timestamp, log_level, message.to_string()); @@ -87,15 +84,16 @@ impl Logger { struct KernelLogger; impl Log for KernelLogger { - fn enabled(&self, _metadata: &log::Metadata) -> bool { - // 这里可以自定义日志过滤规则 - true + fn enabled(&self, metadata: &log::Metadata) -> bool { + // 根据全局日志级别过滤日志 + KERNEL_LOG_LEVEL.should_print(metadata.level().into()) } fn log(&self, record: &log::Record) { if self.enabled(record.metadata()) { - // todo: 接入kmsg + // 记录到 kmsg 缓冲区 Self::kernel_log(record); + // 输出到控制台 Self::iodisplay(record) } } @@ -131,8 +129,8 @@ impl KernelLogger { fn kernel_log(record: &log::Record) { match record.level() { - Level::Debug => Logger.log( - 7, + Level::Debug | Level::Trace => Logger.log( + LogLevel::DEBUG, format_args!( "({}:{})\t {}\n", record.file().unwrap_or(""), @@ -141,7 +139,7 @@ impl KernelLogger { ), ), Level::Error => Logger.log( - 3, + LogLevel::ERR, format_args!( "({}:{})\t {}\n", record.file().unwrap_or(""), @@ -150,7 +148,7 @@ impl KernelLogger { ), ), Level::Info => Logger.log( - 6, + LogLevel::INFO, format_args!( "({}:{})\t {}\n", record.file().unwrap_or(""), @@ -159,7 +157,7 @@ impl KernelLogger { ), ), Level::Warn => Logger.log( - 4, + LogLevel::WARN, format_args!( "({}:{})\t {}\n", record.file().unwrap_or(""), @@ -167,9 +165,6 @@ impl KernelLogger { record.args() ), ), - Level::Trace => { - todo!() - } } } } diff --git a/tools/run-qemu.sh b/tools/run-qemu.sh index 92da8d602..cd12a8f93 100755 --- a/tools/run-qemu.sh +++ b/tools/run-qemu.sh @@ -1,3 +1,17 @@ +#!/bin/bash +# +# DragonOS QEMU启动脚本 +# +# 环境变量支持: +# - DRAGONOS_LOGLEVEL: 设置内核日志级别 (0-7) +# 0: EMERG 1: ALERT 2: CRIT 3: ERR +# 4: WARN 5: NOTICE 6: INFO 7: DEBUG +# 示例: export DRAGONOS_LOGLEVEL=4 # 只显示WARN及以上级别的日志 +# +# - AUTO_TEST: 自动测试选项 +# - SYSCALL_TEST_DIR: 系统调用测试目录 +# + check_dependencies() { # Check if qemu is installed @@ -201,9 +215,30 @@ setup_kernel_init_program() { fi } +# 检测环境变量并设置内核命令行参数 +setup_kernel_cmdline_from_env() { + # 检测 DRAGONOS_LOGLEVEL 环境变量 + # 设置内核日志级别,支持0-7: + # 0: EMERG 1: ALERT 2: CRIT 3: ERR + # 4: WARN 5: NOTICE 6: INFO 7: DEBUG + if [ -n "${DRAGONOS_LOGLEVEL}" ]; then + KERNEL_CMDLINE+=" loglevel=${DRAGONOS_LOGLEVEL} " + echo "[INFO] Setting kernel loglevel to ${DRAGONOS_LOGLEVEL} from environment variable" + fi + + # 检测其他环境变量可以在这里添加 + # 例如: + # if [ -n "${DRAGONOS_DEBUG}" ]; then + # KERNEL_CMDLINE+=" debug " + # fi +} + # 设置内核init程序 setup_kernel_init_program +# 从环境变量设置内核命令行参数 +setup_kernel_cmdline_from_env + if [ ${QEMU_NOGRAPHIC} == true ]; then QEMU_SERIAL=" -serial chardev:mux -monitor chardev:mux -chardev stdio,id=mux,mux=on,signal=off,logfile=${QEMU_SERIAL_LOG_FILE} "