rCore 第七章
管道
在引入文件系统后,我们使用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
传入的参数argc
和argv
进行分析,构造成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
队列中,并且没有被屏蔽。如果现在这个进程没有正在处理信号,那么这个信号肯定没屏蔽的;否则,正在处理的信号可能将这个判断的信号进行了屏蔽。
接着在处理函数进行了写死,SIGKILL
、SIGSTOP
、SIGCONT
、SIGDEF
由内核进行处理:
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;
}
}
}
除了SIGSTOP
和SIGCONT
进行特殊处理,其他的直接将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
}
}