rcore-os/zCore

[bug] Linux program cannot receive interrupt while running.

wfly1998 opened this issue · 4 comments

Environment: QEMU
Command: cd zCore && make run linux=1
File: zCore/src/main.rs

#[cfg(feature = "linux")]
fn main(ramfs_data: &'static mut [u8], _cmdline: &str) {
    use alloc::sync::Arc;
    use alloc::vec;
    use linux_object::fs::MemBuf;

    let args = vec!["/bin/loop".into()];    // <-- I'VE EDITED HERE
    let envs = vec!["PATH=/usr/sbin:/usr/bin:/sbin:/bin:/usr/x86_64-alpine-linux-musl/bin".into()];

    let device = Arc::new(MemBuf::new(ramfs_data));
    let rootfs = rcore_fs_sfs::SimpleFileSystem::open(device).unwrap();
    let _proc = linux_loader::run(args, envs, rootfs);
    run();
}

fn run() -> ! {
    loop {
        executor::run_until_idle();    // <-- PROBLEM OCCURS HERE
        x86_64::instructions::interrupts::enable_interrupts_and_hlt();    // <-- WHEN PROGRAM BLOCKS, IT WILL NEVER RUN
        x86_64::instructions::interrupts::disable();
        info!("I'm running.");    // <-- I'VE EDITED HERE
    }
}

Noticed that I replace args from busybox to loop,the code of loop is following:

#include <stdio.h>
int main()
{
	while (1);
	return 0;
}

When a zircon program is running, the info "I'm running" displays.

But when a linux program is running, before it ends, the info will never display, which also means that interrupt is not enabled, the program will never receive interrupt when running.

And when I turn log level to trace, busybox can receive timer interrupt but loop cannot.

The executor::run_until_idle() function will keep running tasks in the ready queue, until the queue becomes empty.
During this time the interrupt keeps disabled, to avoid handling difficult situations.
So when running your loop program, the task will always be in the ready queue and the function will never return.
A possible solution is to enable interrupt every time before running a task in the executor.

I even edited executor::run_until_idle() to following:

/// Run futures until there is no runnable task.
pub fn run_until_idle() {
    while let Some(task) = { || GLOBAL_EXECUTOR.lock().pop_runnable_task() }() {
        x86_64::instructions::interrupts::enable();    // <-- ENABLE INTERRUPT
        task.mark_sleep();
        x86_64::instructions::interrupts::enable();    // <-- ENABLE INTERRUPT
        // make a waker for our task
        let waker = waker_ref(&task);
        x86_64::instructions::interrupts::enable();    // <-- ENABLE INTERRUPT
        // poll our future and give it a waker
        let mut context = Context::from_waker(&*waker);
        x86_64::instructions::interrupts::enable();    // <-- ENABLE INTERRUPT
        let ret = task.future.lock().as_mut().poll(&mut context);
        x86_64::instructions::interrupts::enable();    // <-- ENABLE INTERRUPT
        if let Poll::Pending = ret {
            GLOBAL_EXECUTOR.lock().push_task(task);
            x86_64::instructions::interrupts::enable();    // <-- ENABLE INTERRUPT
        }
        x86_64::instructions::interrupts::disable();
    }
}

Interrupt still doesn't work...

It's my fault, I've thought that trap_handler is the entry of all interrupt, but it's only the entry of kernel interrupt...

Entry of user interrupt is context_run in kernel-hal-bare/src/lib.rs.