管道

​ 在引入文件系统后,我们使用File Trait对于一个进程可以读写的文件进行说明,基于File Trait我们实现管道,它可以将不同进程的输入和输出进行连接。我们为用户提供sys_pipe接口,用户可以通过这个接口,获取到一对读写口。

pub fn sys_pipe(pipe: *mut usize) -> isize;

​ 我们只需要将pipe设置为一种特殊的File Trait, 在内核空间中会有如下过程,

pub fn make_pipe() -> (Arc<Pipe>, Arc<Pipe>) {
    let buffer = Arc::new(unsafe { UPSafeCell::new(PipeRingBuffer::new()) });
    let read_end = Arc::new(Pipe::read_end_with_buffer(buffer.clone()));
    let write_end = Arc::new(Pipe::write_end_with_buffer(buffer.clone()));
    buffer.exclusive_access().set_write_end(&write_end);
    (read_end, write_end)
}

​ 这个读口和写口会独自占用一个fd_table中文件描述符号,通过文件描述符可以读写内核空间中的管道,这样一旦调用fork就可以读写内核中同一ringbuffer。为了保证写口被关闭时,直接返回,我们可以通过ringbuffer中的weak连接判断写口是否已经关闭。

​ 如下是这个可阻塞的ringbuffer的部分代码示例,

let mut ring_buffer = self.buffer.exclusive_access();
let loop_read = ring_buffer.available_read();
if loop_read = 0 {
    if ring_buffer.all_write_ends_closed() {
        return already_read;
    }
    drop(ring_buffer);
    suspend_current_and_run_next();
    continue;
}
for _ in 0..loop_read {
	if let Some(byte_ref) = buf_iter.next() {
        unsafe {
            *byte_ref = ring_buffer.read_byte();
        }
        already_byte += 1;
        if already_read == want_to_read {
            return want_to_read;
        }
    } else {
        return already_read;
    }
}

​ 如果缓冲区中已经为空,那么就要进行阻塞调度下一个进程,但是写口关闭我们直接返回;否则,在buffer中有多少字符读多少字符。

输入输出重定向

为了实现cat工具来验证我们的输入输出重定向,我们必须介绍参数是如何通过exec系统调用传入最后进程的用户栈的。首先shell要将ls -lia分成两个字符串进行传入,那么在exec系统调用是会将上图所示的结构压入到用户栈,由于一些机器不支持非对其访存,可能引发异常,所以我们这里进行一次对齐。之后,执行这个应用程序时,运行时库会将exec传入的参数argcargv进行分析,构造成str引用的各式,最后传入到我们的应用程序。

​ 之后就是重定向本身的实现,我们需要新添加一个系统调用sys_dup,它接受一个文件描述符号,并且拷贝它。我们由于关闭了stdout,那么新的fd号就可以替代原来stdout的位置,从而达成重定向。

信号

这里是rCore中所用到的信号的数据结构,我们用bitflags来实现一个信号变量,每一个bit表示一个信号,同时定义SignalAction, 定义来信号对应的处理函数和执行这个信号handler时需要屏蔽的信号。最后是添加了信号后,我们需要扩展我们的TaskControlBlock:

  • 这里的signals 表示pending等待处理的信号。
  • signal_mask, 表示进程屏蔽的信号。
  • handling_sig, 表示正在处理的信号。
  • killed,表示目前进程是否要被杀除。
  • frozen, 表示目前进程是否因为信号被阻塞。
  • trap_cx_backup,表示调用用户的signal handler时,我们需要存储trap上下文。

根据上图的流程,我们展示trap文件中的内容

 //...
	Trap::Exception(Exception::StoreFault)
        | Trap::Exception(Exception::StorePageFault)
        | Trap::Exception(Exception::InstructionFault)
        | Trap::Exception(Exception::InstructionPageFault)
        | Trap::Exception(Exception::LoadFault)
        | Trap::Exception(Exception::LoadPageFault) => {
            println!(
                "[kernel] {:?} in application, bad addr = {:#x}, bad instruction = {:#x}, kernel killed it.",                scause.cause(),
                stval, 
                current_trap_cx().sepc
            );
            //exit_current_and_run_next(-2);
			current_add_signal(SignalFlags::SIGSEGV);
        }
        Trap::Exception(Exception::IllegalInstruction) => {
            println!("[kernel] IllegalInstruction in application, kernel killed it.");
            //exit_current_and_run_next(-3);
			current_add_signal(SignalFlags::SIGILL);
        }
        Trap::Interrupt(Interrupt::SupervisorTimer) => {
            set_next_trigger();
            suspend_current_and_run_next();
        }
//...

​ 我们可以看到在发生一些异常时,我们将信号添加到了该进程的信号pending队列中,这样之后我们就可以检查到了。

​ 同时信号除了上述的产生方式,还有两种异步的产生方式:

  • 进程通过kill给自己或者其他的进程发送信号
  • 内核检测到某些事件,然后给某些进程发信号,这里没有实现。
pub fn sys_kill(pid: usize, signum: i32) -> isize {
    if let Some(task) = pid2task(pid) {
        if let Some(flag) = SignalFlags::from_bits(1 << signum) {
            //...
            task_ref.signals.insert(flag);
           	0
        } else {
            -1
        }
    } //...
}

​ 而信号的处理,由两个主要函数:

pub fn handle_signals() {
    loop {
        check_pending_signals();
        let (frozen, killed) = {
            let task = current_task().unwrap();
            let task_inner = task.inner_exclusive_access();
            (task_inner.frozen, task_inner.killed)
        };
        if !frozen || killed {
            break;
        }
        suspend_current_and_run_next();
    }
}

​ 在trap结束前,会执行handle_signals这个函数。同时

fn check_pending_signals() {
    for sig in 0..(MAX_SIG+1) {
        let task = current_task().unwrap();
        let task_inner = task.inner_exclusive_access();
        let signal = SignalFlags::from_bits(1 << sig).unwrap();
        if task_inner.signals.contains(signal) && (!task_inner.signal_mask.contains(signal)) {
            let mut masked = true;
            let handling_sig = task_inner.handling_sig;
            if handling_sig == -1 {
                masked = false;
            } else {
                let handling_sig = handling_sig as usize;
                if !task_inner.signal_actions.table[handling_sig]
                	.masl
                	.contains(signal)
                {
                    masked = false;
                }
            }
            if !masked {
                drop(task_inner);
                drop(task);
                if signal == SignalFlags::SIGKILL
                	|| signal == SignalFlags::SIGSTOP
                	|| signal == SignalFlags::SIGCONT
                	|| signal == SignalFlags::SIGDEF
                {
                    call_kernel_signal_handler(signal);
                } else {
                    call_user_signal_handler(sig, signal);
                    return;
                }
            }
        }
    }
}

​ 在check_pending_signals这个函数中,所有的信号都会被检查一遍,如果这个信号在pending队列中,并且没有被屏蔽。如果现在这个进程没有正在处理信号,那么这个信号肯定没屏蔽的;否则,正在处理的信号可能将这个判断的信号进行了屏蔽。

​ 接着在处理函数进行了写死,SIGKILLSIGSTOPSIGCONTSIGDEF由内核进行处理:

fn call_kernel_signal_handler(signal: SignalFlags) {
    let task = current_task().unwrap();
    let mut task_inner = task.inner_exclusive_access();
    match signal {
        SignalFlags::SIGSTOP => {
            task_inner.frozen = true;
            task_inner.signals ^= SignalFlags::SIGSTOP;
        }
        SignalFlags::SIGCONT => {
            if task_inner.signals.contains(SignalFlags::SIGCONT) {
                task_inner.signals ^= SignalFlags::SIGCONT;
                task_inner.frozen = false;
            }
        }
        _ => {
            task_inner.killed = true;
        }
    }
}

​ 除了SIGSTOPSIGCONT进行特殊处理,其他的直接将killed置位进行杀除。

​ 否则调用user的处理函数:

fn call_user_signal_handler(sig: usize, signal: SignalFlags) {
    let task = current_task().unwrap();
    let mut task_inner = task.inner_exclusive_access();
    
    let handler = task_inner.signal_actions.table[sig].handler;
    if handler != 0 {
        task_inner.handling_sig = sig as isize;
        task_inner.signals ^= signal;
        
        let mut trap_ctx = task_inner.get_trap_cx();
        task_inner.trap_ctx_backup = Some(*trap_ctx);
        
        trap_ctx.sepc = handler;
        
        trap_ctx.x[10] = sig;
    } else {
        //...
    }
}

​ 这里如果是user_handler的话,需要获取处理函数的地址,同时保存现在的上下文,最后将上下文中的返回值设置为sig。执行完这些函数check_pending_signals将会返回。

handle_signals();

if let Some((errno, msg)) = check_signals_error_of_current() {
	println!("[kernel] {}", msg);
	exit_current_and_run_next(errno);
}

trap_return();

​ 我们看到,如果发现信号中包含的错误信息,会直接退出该进程。

​ 最后是用户如何在处理完信号之后进行返回,

pub fn sys_sigreturn() -> isize {
	if let Some(task) = current_task() {
        let mut inner = task.inner_exclusvie_access();
        inner.handling_sig = -1;
        let trap_ctx = inner.get_trap_cx();
        *trap_ctx = inner.trap_ctx_back.unwrap();
        trap_ctx.x[10] as isize
    } else {
        -1
    }
}