In usual shell, | is the symbolic of pipe. Manage input from left and output to right. If we abstract everything to file, s.t. Stdin or Stdout, so does Pipe, it has read and write ends, user could read thing from this end and write thing(often in child process) to other end, transfer those underneath thing.
We already has file descriptor as the indication of file, we will implement same operation for pipe.
sys_pipe get the ptr of a array with len = 2, output the write and the read ends of descriptors of pipe in the ptr.
It should has write and read ends which means ends share the same data, and record read and write informations on this data. We will construct RingBuffer to achieve this. Pipe owns a buffer control read and write, buffer will record data from head to tail index. Why we can’t just use two piece of data or Queue?
Because there’s no copy and suitable for our restriction! We will read data from head and move forward and push data to end in a fixed array rather allocation for Queue.
pubstructPipeRingBuffer { arr: [u8; RING_BUFFER_SIZE], head: usize, // head index of ring buffer tail: usize, // tail index of ring buffer status: RingBufferStatus, write_end: Option<Weak<Pipe>>, }
impl TaskControlBlock { // notice exec will allocate a new memory set! pubfnexec(&self, elf_data: &[u8], args: Vec<String>) { // ... // first allocate memory for ptr of strings. user_sp -= (args.len() + 1) * core::mem::size_of::<usize>(); let argv_base = user_sp; // allocate new memory in user stack addr as a vector of strings letmut argv: Vec<_> = (0..=args.len()) .map(|arg| { translated_refmut( memory_set.token(), (argv_base + arg * core::mem::size_of::<usize>()) as *mutusize ) }) .collect(); *argv[args.len()] = 0; for i in0..args.len() { // allocate for strings themselves. user_sp -= args[i].len() + 1; *argv[i] = user_sp; letmut p = user_sp; for c in args[i].as_bytes() { *translated_refmut(memory_set.token(), p as *mutu8) = *c; p += 1; } *translated_refmut(memory_set.token(), p as *mutu8) = 0; } // make the user_sp aligned to 8B for k210 platform user_sp -= user_sp % core::mem::size_of::<usize>();
// **** hold current PCB lock letmut inner = self.acquire_inner_lock(); // substitute memory_set inner.memory_set = memory_set; // update trap_cx ppn inner.trap_cx_ppn = trap_cx_ppn; // initialize trap_cx letmut trap_cx = TrapContext::app_init_context( entry_point, user_sp, KERNEL_SPACE.lock().token(), self.kernel_stack.get_top(), trap_handler asusize, ); // a[0] be args len trap_cx.x[10] = args.len(); // a[1] be args base addr trap_cx.x[11] = argv_base; *inner.get_trap_cx() = trap_cx; // **** release current PCB lock } }
Now we provide receive operation in _start, in which main could use it at first time S-level reading data and passing to U-level:
#[no_mangle] #[link_section = ".text.entry"] pubextern"C"fn_start(argc: usize, argv: usize) -> ! { unsafe { HEAP.lock() .init(HEAP_SPACE.as_ptr() asusize, USER_HEAP_SIZE); } letmut v: Vec<&'staticstr> = Vec::new(); for i in0..argc { let str_start = unsafe { ((argv + i * core::mem::size_of::<usize>()) as *constusize).read_volatile() }; let len = (0usize..).find(|i| unsafe { ((str_start + *i) as *constu8).read_volatile() == 0 }).unwrap(); v.push( core::str::from_utf8(unsafe { core::slice::from_raw_parts(str_start as *constu8, len) }).unwrap() ); } exit(main(argc, v.as_slice())); }
Redirection
Redirection usually represent using > and < for output and input.
If we really want to redirect IO, we will combine user_shell and sys_dup.
First, sys_dup will duplicate a new file descriptor already opened in this process.
Then we parse user arguments, if there exist > or <, fork a new child process, open the file and close our corresponding Stdin and Stdout descriptor, using dup to hold the place of it by file itself! Then exec by original parsed arguments, and receive results in parent process.