开发DragonOS日志--开发屏幕管理器(scm)与文本显示框架(textui)

文章详细描述了对textui文本显示框架和scm屏幕管理器进行重构的过程,包括消除全局变量、减少unsafe使用、提高代码可读性和调试性。在重构过程中,作者解决了字符渲染、内存访问和双缓冲区导致的问题,如镜像错误、位置更新错误和内存分配错误。通过调试和优化,最终实现了正常运行。
摘要由CSDN通过智能技术生成

主要工作

主要对textui(文本显示框架)和scm(屏幕管理器)进行重构,主要是将代码结构重新设计一番,利用结构体将特定数据封装,消除一些全局变量和unsafe的使用次数,提高代码的可读性,方便调试,并且重新分配函数功能,修改一些以前调试时难以修复的错误

首先再次理解一下textui(文本显示框架)和scm(屏幕管理器)的结构,便于之后的修改。

Scm::安装,卸载,管理各种框架(每个框架只有一个),

Textui:管理窗口显示(有多个窗口),因为只有一个文本框架,所以有个文本框架全局变量来储存当前文本框架信息

问题与解决方法

1,把字符的渲染到输出缓冲区与输出缓冲区的一些地址操作未分离开,导致一些错误发生且难以调试,例如:   

/**

     * @brief 将该字符对象输出到缓冲区

     *

     * @param line_id 要放入的真实行号

     * @param index 要放入的真实列号

     */

    pub fn textui_refresh_character(

        &self,

        line_id: LineId,

        index: LineIndex,

    ) -> Result<i32, SystemError> {

        // 找到要渲染的字符的像素点数据

        let font: Font = Font::get_font(self.c as usize);



        //   x 左上角列像素点位置

        //   y 左上角行像素点位置

        let index_x: u32 = index.into();

        let x: u32 = index_x * TEXTUI_CHAR_WIDTH;



        let id_y: u32 = line_id.into();

        let y: u32 = id_y * TEXTUI_CHAR_HEIGHT;



        let count = (*BUF_WIDTH.read() * y + x) as isize;

       

        let mut addr = BufAddr::get_buf_addr_by_offset(count);



        // 在缓冲区画出一个字体,每个字体有TEXTUI_CHAR_HEIGHT行,TEXTUI_CHAR_WIDTH列个像素点

        for i in 0..TEXTUI_CHAR_HEIGHT {

            //计算出帧缓冲区每一行打印的起始位置的地址(起始位置+(y+i)*缓冲区的宽度+x)



            for j in 0..TEXTUI_CHAR_WIDTH {

                let buf_addr: *mut usize = addr.into();

                if font.is_frcolor(i as usize, j as usize) {

                    // 字,显示前景色

                    unsafe { *buf_addr = self.frcolor.into() };

                } else {

                    // 背景色

                    unsafe { *buf_addr = self.bkcolor.into() };

                }

                addr.add(1);

            }

            addr.add(*BUF_WIDTH.read() as isize);

        }

        return Ok(0);

    }

解决方法:类似文件读写操作一样,将原本字符渲染过程中的裸指针操作,通过用core::slice::from_raw_parts_mut(data, len)转换成一个&mut[u32](每个元素对应一个像素点的颜色),从而可以直接往像素点数组中写入颜色,而不需通过裸指针操作,大大降低bug.

本来是想直接把缓冲区放进ScmBufferInfo中,但是后来发现这样会导致后面操作变得复杂,而且也不利于实现clone等trail(因为引入生命周期和&mut),而且我也只需要一个地址就可以转换成&mut[u32],所以为何不把这个操作往后延,在我需要它时,再把他弄出来,这样简化了前面的操作,不至于为了我不需要的东西而是操作更复杂。

所以再我需要它的地方搞出一个结构体来使用它(目前是textui):

#[derive(Debug)]

pub struct TextuiBuf<'a>(&'a mut [u32]);

impl TextuiBuf<'_> {

    pub fn new(buf: &mut [u32]) -> TextuiBuf {

        TextuiBuf(buf)

    }

    pub fn get_buf_from_vaddr(vaddr: usize, len: usize) -> TextuiBuf<'static> {

        let new_buf: &mut [u32] =

            unsafe { core::slice::from_raw_parts_mut(vaddr as *mut u32, len) };

        let buf: TextuiBuf<'_> = TextuiBuf::new(new_buf);

        return buf;

    }

    pub fn get_buf_from_video_frame_buffer_info() -> TextuiBuf<'static> {

        let len =

            unsafe { video_frame_buffer_info.width * video_frame_buffer_info.height } as usize;

        TextuiBuf::get_buf_from_vaddr(unsafe { video_frame_buffer_info.vaddr }, len)

    }

    pub fn put_color_in_pixel(&mut self, color: u32, index: usize) {

        let buf: &mut [u32] = self.0;

        buf[index] = color;

    }

    pub fn get_index_of_next_line(now_index: usize) -> usize {

        *(BUF_WIDTH.read()) as usize + now_index

    }

    pub fn get_index_by_x_y(x: usize, y: usize) -> usize {

        *(BUF_WIDTH.read()) as usize * y + x

    }

    pub fn get_start_index_by_lineid_lineindex(lineid: LineId, lineindex: LineIndex) -> usize {

        //   x 左上角列像素点位置

        //   y 左上角行像素点位置

        let index_x: u32 = lineindex.into();

        let x: u32 = index_x * TEXTUI_CHAR_WIDTH;



        let id_y: u32 = lineid.into();

        let y: u32 = id_y * TEXTUI_CHAR_HEIGHT;



        TextuiBuf::get_index_by_x_y(x as usize, y as usize)

    }

}

顺便把得到字体也封装起来:

#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]

pub struct Font([u8; 16]);

impl Font {

    pub fn get_font(index: usize) -> Font {

        Self(unsafe { font_ascii[index] })

    }

    pub fn is_frcolor(&self, height: usize, width: usize) -> bool {

        let w = self.0[height];

        let testbit = 1 << width;

        w & testbit != 0

    }

}

然后先前打印字符的操作便可变成这样:

pub fn textui_refresh_character(

        &self,

        lineid: LineId,

        lineindex: LineIndex,

    ) -> Result<i32, SystemError> {

        // 找到要渲染的字符的像素点数据

        let font: Font = Font::get_font(self.c as usize);



        let mut count = TextuiBuf::get_start_index_by_lineid_lineindex(lineid, lineindex);



        // let buf=TEXTUI_BUF.lock();

        let vaddr = *BUF_VADDR.read();

        let len = *BUF_SIZE.read();

        let mut buf = TextuiBuf::get_buf_from_vaddr(vaddr, len);

        // 在缓冲区画出一个字体,每个字体有TEXTUI_CHAR_HEIGHT行,TEXTUI_CHAR_WIDTH列个像素点

        for i in 0..TEXTUI_CHAR_HEIGHT {

           

            let start = count;

            for j in 0..TEXTUI_CHAR_WIDTH {

                if font.is_frcolor(i as usize, j as usize) {

                    // 字,显示前景色

                    buf.put_color_in_pixel(self.frcolor.into(), count);

                } else {

                    // 背景色

                    buf.put_color_in_pixel(self.bkcolor.into(), count);

                }

                count += 1;

            }

            count = TextuiBuf::get_index_of_next_line(start);

        }

        return Ok(0);

    }

注意这里使用读写锁来封装全局变量BUF_WIDTH,BUF_VADDR,BUF_SIZE是因为它们的修改次数很少(一般在框架获得屏幕控制权才修改),而打印字符需要读取这些信息,为了降低开销,故使用读写锁

  1. 解决完上面问题,运行时却发现出现打印镜像问题,例如 (打印“3”出现)

解决方法:应该是Font的这个方法出现了问题,导致横向出现镜像

    pub fn is_frcolor(&self, height: usize, width: usize) -> bool {

        let w = self.0[height];

        let testbit = 1 << width;

        w & testbit != 0

    }

因为第一个像素是在w的最高位,所以应该是let testbit = 1 <<(8 -width);

修改后成功

  1. 输入 kinfo("555555555");只显示出一个字符 ,但是串口那里显示出

解决方法:发现输入kinfo("555555553"),却显示出 ,说明应该是每次打印的位置没有变,导致覆盖了前面打印的字符,可能是LineIndex的方法的原因  

fn add(mut self, num: i32) {//不知为何没有修改自身

        self.0 += num;

    }

将其修改成

impl core::ops::Add<i32> for LineIndex {

    type Output = Self;

    fn add(self, rhs: i32) -> Self::Output {

        LineIndex::new(self.0 + rhs)

    }

}

却发现输出不了字符 kinfo("555555553");

但是串口上有显示,那就一定是刚才修改有问题,调试一番,发现是LineIndex的方法的错误,应修改成:

impl Add<i32> for LineIndex {

    // type Output = Self;//这个不知为什么不对

    type Output = LineIndex;

    fn add(self, rhs: i32) -> Self::Output {

        LineIndex::new(self.0 + rhs)

    }

}

4,在main中输入:

    for (int i = 0; i < 100; i++)

    {

        // kinfo("Cpu_max_phys_addrline_size =%d",i);

        kinfo("1234567890123456789012345678901234567890");

    }

发现每次都是输出了2258个字符后便停止输出,而且串口也是输出同样信息便停止了

调试发现在这里卡住:

fn true_textui_putchar(

    character: u8,

    fr_color: FontColor,

    bk_color: FontColor,

) -> Result<i32, SystemError> {

    if unsafe { TEST_IS_INIT } {

        c_uart_send_str(UartPort::COM1.to_u16(), "putchar33333333333\n\0".as_ptr());

        let val = WINDOW_MPSC.window_r.try_recv_ref();

       

        let mut window: TextuiWindow;

        if let Err(err) = val {

            match err {

                TryRecvError::Empty => {

                    window = CURRENT_WINDOW.clone();

                }

                _ => {

                    c_uart_send_str(

                        UartPort::COM1.to_u16(),

                        "true_textui_putchar fail\n\0".as_ptr(),

                    );

                    todo!();

                }

            }

        } else {

        c_uart_send_str(UartPort::COM1.to_u16(), "putchar777777777777\n\0".as_ptr());

            let r = val.unwrap();

        c_uart_send_str(UartPort::COM1.to_u16(), "putchar888888888888\n\0".as_ptr());

            window = r.clone();//卡在这里,不知为何

        c_uart_send_str(UartPort::COM1.to_u16(), "putchar6666666666\n\0".as_ptr());

        }

发现是因为这里设置的mpsc_buf_size太大,调小一些便可运行;

//pub const MPSC_BUF_SIZE: usize = 4096;

pub const MPSC_BUF_SIZE: usize = 2048;

impl WindowMpsc {

    fn new() -> Self {

        // let window = &TEXTUI_PRIVATE_INFO.lock().current_window;

        let (window_s, window_r) = mpsc::channel::<TextuiWindow>(MPSC_BUF_SIZE);

        WindowMpsc { window_r, window_s }

    }

}

5,发现启用了双缓冲区后显示出现乱码:

关掉双缓存区后,正常运行

6,输入about,发现内存错误

系统错误报告内容如下:

  • 在 mm.c 文件的第 368 行出现了 BUG 错误:Cannot alloc page, ZONE=2 nums=1, mm_total_2M_pages=254
  • 在执行 do_page_fault() 函数时发生了错误,错误代码为 0x0000000000000000。
  • 导致错误的原因是,访问了一个不存在的内存页(Page Not-Present),并且是在 supervisor 模式下出现的。
  • 出错的 CPU 编号为 0,进程 ID 为 1。
  • 触发错误的地址是 0x0000000000000008。
  • 接下来是一段核心回溯(Kernel traceback),显示了该错误发生时正在执行的函数以及对应的内存地址和堆栈指针。

经过调试发现再次调小mpsc_buf_size=512,可以调用about成功

但是如果再次调用about却发现又出现乱码报错

发现根本原因还是踩内存,因为未初始化textui之前, video_frame_buffer_info.vaddr = 0xffff800003000000;

它前面只有48M的内存供其他模块使用,但是现在大概率其他模块使用的内存在未初始化textui之前(之后video_frame_buffer_info.vaddr会重新映射到其他地方)超过了48M写在了显示缓冲区,这就导致之后往窗口打印信息时会刷新缓冲区,也就修改其他模块保留的信息,故而报错,所以解决办法是在未初始化textui之前其他模块使用的内存将要到达48M时,禁止往窗口打印信息,在初始化textui之后再重新允许。但是遗憾的是还是出现一样的错误,而且调大video.c中video_init方法中的分配的地址,发现依然是出现上述问题,说明应该不是这个原因。

再次调试发现video_frame_buffer_info.vaddr在执行scm_reinit方法前后并没有发生变化,都是50331648,发现是因为这里本来就设置的一样(之后发现其实不一样,因为打印使用了“%d”发生溢出,所以一样,需要使用“%#018lx”来打印(将一个长整型数以十六进制形式打印,并固定输出18位长度)地址)

看来还是得解决好双缓冲区的问题,经过调试,发现当process.c中的initial_kernel_thread

方法执行到scm_enable_double_buffer()之后,便停止往窗口输出字符

再次调试发现在true_scm_enable_double_buffer()在执行完开启双缓冲区语句后添加一些输出语句后再打断点,发现窗口上出现了打印语句

可是如果在之后的scm_enable_double_buffer()在执行完true_ scm_enable_double_buffer()之后添加打印语句kinfo!()再添加断点,却发现不能在窗口输出打印语句并报错

但是如果是输出打印语句println!()却可以在窗口输出

根据报错提醒,定位问题应该是在video_refresh_daemon的memcpy((void *)video_frame_buffer_info.vaddr, (void *)video_refresh_target->vaddr,

video_refresh_target->size);

发现在执行video_refresh_daemon时,video_refresh_target->vaddr为0,但是这个已经在video_set_refresh_target()之后,而且未执行video_refresh_daemon之前video_refresh_target->vaddr也被修改为0xffff800009800000,不知为何在video_refresh_daemon时,video_refresh_target->vaddr却为0。

最后发现原来是因为传入的是指向 target_buf的栈上内存指针,在此调用方法(ture_scm_enable_double_buffer())之后,便会释放该指针指向的内存,造成内存泄漏,形成上面的现象

所以需要将指针去掉,改成全局变量就行

之后执行时便发现正常运行

但是吧还是出现之前那个问题

调试发现是在这里出现问题,不知为何第二次执行时输出字符会出现错误

调试问题主要是以前代码的遗留问题,需要注释掉     process_exit_mm(pcb);(释放进程的页表的方法有问题)

至此代码调试正常通过

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值