Unpack LineDiscipline from Arc

This commit is contained in:
Ruihan Li 2025-05-03 17:27:56 +08:00 committed by Tate, Hongliang Tian
parent 9e2dde5ebb
commit 45258d0475
5 changed files with 88 additions and 118 deletions

View File

@ -6,7 +6,7 @@ use ostd::task::Task;
use crate::{
current_userspace,
device::tty::{line_discipline::LineDiscipline, new_job_control_and_ldisc},
device::tty::line_discipline::LineDiscipline,
events::IoEvents,
fs::{
device::{Device, DeviceId, DeviceType},
@ -18,6 +18,7 @@ use crate::{
},
prelude::*,
process::{
broadcast_signal_async,
posix_thread::{AsPosixThread, AsThreadLocal},
signal::{PollHandle, Pollable, Pollee},
JobControl, Terminal,
@ -43,10 +44,9 @@ pub struct PtyMaster {
impl PtyMaster {
pub fn new(ptmx: Arc<dyn Inode>, index: u32) -> Arc<Self> {
Arc::new_cyclic(move |master| {
let (job_control, ldisc) = new_job_control_and_ldisc();
let slave = Arc::new_cyclic(move |weak_self| PtySlave {
ldisc,
job_control,
ldisc: LineDiscipline::new(),
job_control: JobControl::new(),
master: master.clone(),
weak_self: weak_self.clone(),
});
@ -166,11 +166,19 @@ impl FileIo for PtyMaster {
let write_len = buf.len();
let mut input = self.input.lock();
for character in buf {
self.slave.ldisc.push_char(character, |content| {
for byte in content.as_bytes() {
input.push_overwrite(*byte);
}
});
self.slave.ldisc.push_char(
character,
|signum| {
if let Some(foreground) = self.slave.job_control.foreground() {
broadcast_signal_async(Arc::downgrade(&foreground), signum);
}
},
|content| {
for byte in content.as_bytes() {
input.push_overwrite(*byte);
}
},
);
}
self.pollee.notify(IoEvents::IN);
@ -240,8 +248,8 @@ impl Drop for PtyMaster {
}
pub struct PtySlave {
ldisc: Arc<LineDiscipline>,
job_control: Arc<JobControl>,
ldisc: LineDiscipline,
job_control: JobControl,
master: Weak<PtyMaster>,
weak_self: Weak<Self>,
}

View File

@ -2,10 +2,7 @@
use alloc::format;
use ostd::{
sync::LocalIrqDisabled,
trap::{disable_local, in_interrupt_context},
};
use ostd::{sync::LocalIrqDisabled, trap::disable_local};
use super::termio::{KernelTermios, WinSize, CC_C_CHAR};
use crate::{
@ -13,10 +10,9 @@ use crate::{
prelude::*,
process::signal::{
constants::{SIGINT, SIGQUIT},
signals::kernel::KernelSignal,
sig_num::SigNum,
PollHandle, Pollable, Pollee,
},
thread::work_queue::{submit_work_item, work_item::WorkItem, WorkPriority},
util::ring_buffer::RingBuffer,
};
@ -25,8 +21,6 @@ use crate::{
const BUFFER_CAPACITY: usize = 4096;
pub type LdiscSignalSender = Arc<dyn Fn(KernelSignal) + Send + Sync + 'static>;
// Lock ordering to prevent deadlock (circular dependencies):
// 1. `termios`
// 2. `current_line`
@ -43,12 +37,6 @@ pub struct LineDiscipline {
winsize: SpinLock<WinSize, LocalIrqDisabled>,
/// Pollee
pollee: Pollee,
/// Used to send signal for foreground processes, when some char comes.
send_signal: LdiscSignalSender,
/// Work item
work_item: Arc<WorkItem>,
/// Parameters used by a work item.
work_item_para: Arc<SpinLock<LineDisciplineWorkPara, LocalIrqDisabled>>,
}
pub struct CurrentLine {
@ -99,29 +87,23 @@ impl Pollable for LineDiscipline {
impl LineDiscipline {
/// Creates a new line discipline
pub fn new(send_signal: LdiscSignalSender) -> Arc<Self> {
Arc::new_cyclic(move |line_ref: &Weak<LineDiscipline>| {
let line_discipline = line_ref.clone();
let work_item = WorkItem::new(Box::new(move || {
if let Some(line_discipline) = line_discipline.upgrade() {
line_discipline.send_signal_after();
}
}));
Self {
current_line: SpinLock::new(CurrentLine::default()),
read_buffer: SpinLock::new(RingBuffer::new(BUFFER_CAPACITY)),
termios: SpinLock::new(KernelTermios::default()),
winsize: SpinLock::new(WinSize::default()),
pollee: Pollee::new(),
send_signal,
work_item,
work_item_para: Arc::new(SpinLock::new(LineDisciplineWorkPara::new())),
}
})
pub fn new() -> Self {
Self {
current_line: SpinLock::new(CurrentLine::default()),
read_buffer: SpinLock::new(RingBuffer::new(BUFFER_CAPACITY)),
termios: SpinLock::new(KernelTermios::default()),
winsize: SpinLock::new(WinSize::default()),
pollee: Pollee::new(),
}
}
/// Pushes a char to the line discipline
pub fn push_char<F2: FnMut(&str)>(&self, ch: u8, echo_callback: F2) {
pub fn push_char<F1: FnMut(SigNum), F2: FnMut(&str)>(
&self,
ch: u8,
mut signal_callback: F1,
echo_callback: F2,
) {
let termios = self.termios.lock();
let ch = if termios.contains_icrnl() && ch == b'\r' {
@ -130,8 +112,8 @@ impl LineDiscipline {
ch
};
if self.may_send_signal(&termios, ch) {
submit_work_item(self.work_item.clone(), WorkPriority::High);
if let Some(signum) = char_to_signal(ch, &termios) {
signal_callback(signum);
// CBREAK mode may require the character to be outputted, so just go ahead.
}
@ -180,27 +162,6 @@ impl LineDiscipline {
}
}
fn may_send_signal(&self, termios: &KernelTermios, ch: u8) -> bool {
if !termios.is_canonical_mode() || !termios.contains_isig() {
return false;
}
let signal = match ch {
ch if ch == *termios.get_special_char(CC_C_CHAR::VINTR) => KernelSignal::new(SIGINT),
ch if ch == *termios.get_special_char(CC_C_CHAR::VQUIT) => KernelSignal::new(SIGQUIT),
_ => return false,
};
if in_interrupt_context() {
// `kernel_signal()` may cause sleep, so only construct parameters here.
self.work_item_para.lock().kernel_signal = Some(signal);
} else {
(self.send_signal)(signal);
}
true
}
fn check_io_events(&self) -> IoEvents {
let buffer = self.read_buffer.lock();
@ -211,13 +172,6 @@ impl LineDiscipline {
}
}
/// Sends a signal later. The signal will be handled by a work queue.
fn send_signal_after(&self) {
if let Some(signal) = self.work_item_para.lock().kernel_signal.take() {
(self.send_signal)(signal);
};
}
// TODO: respect output flags
fn output_char<F: FnMut(&str)>(&self, ch: u8, termios: &KernelTermios, mut echo_callback: F) {
match ch {
@ -230,7 +184,7 @@ impl LineDiscipline {
}
ch if is_printable_char(ch) => print!("{}", char::from(ch)),
ch if is_ctrl_char(ch) && termios.contains_echo_ctl() => {
let ctrl_char = format!("^{}", get_printable_char(ch));
let ctrl_char = format!("^{}", ctrl_char_to_printable(ch));
echo_callback(&ctrl_char);
}
_ => {}
@ -393,19 +347,19 @@ fn is_ctrl_char(ch: u8) -> bool {
(0..0x20).contains(&ch)
}
fn get_printable_char(ctrl_char: u8) -> char {
debug_assert!(is_ctrl_char(ctrl_char));
char::from_u32((ctrl_char + b'A' - 1) as u32).unwrap()
}
fn char_to_signal(ch: u8, termios: &KernelTermios) -> Option<SigNum> {
if !termios.is_canonical_mode() || !termios.contains_isig() {
return None;
}
struct LineDisciplineWorkPara {
kernel_signal: Option<KernelSignal>,
}
impl LineDisciplineWorkPara {
fn new() -> Self {
Self {
kernel_signal: None,
}
match ch {
ch if ch == *termios.get_special_char(CC_C_CHAR::VINTR) => Some(SIGINT),
ch if ch == *termios.get_special_char(CC_C_CHAR::VQUIT) => Some(SIGQUIT),
_ => None,
}
}
fn ctrl_char_to_printable(ch: u8) -> char {
debug_assert!(is_ctrl_char(ch));
char::from_u32((ch + b'A' - 1) as u32).unwrap()
}

View File

@ -14,7 +14,8 @@ use crate::{
},
prelude::*,
process::{
signal::{signals::kernel::KernelSignal, PollHandle, Pollable},
broadcast_signal_async,
signal::{PollHandle, Pollable},
JobControl, Terminal,
},
};
@ -40,8 +41,8 @@ pub struct Tty {
#[expect(unused)]
name: CString,
/// line discipline
ldisc: Arc<LineDiscipline>,
job_control: Arc<JobControl>,
ldisc: LineDiscipline,
job_control: JobControl,
/// driver
driver: SpinLock<Weak<TtyDriver>>,
weak_self: Weak<Self>,
@ -49,11 +50,10 @@ pub struct Tty {
impl Tty {
pub fn new(name: CString) -> Arc<Self> {
let (job_control, ldisc) = new_job_control_and_ldisc();
Arc::new_cyclic(move |weak_ref| Tty {
name,
ldisc,
job_control,
ldisc: LineDiscipline::new(),
job_control: JobControl::new(),
driver: SpinLock::new(Weak::new()),
weak_self: weak_ref.clone(),
})
@ -66,8 +66,15 @@ impl Tty {
pub fn push_char(&self, ch: u8) {
// FIXME: Use `early_print` to avoid calling virtio-console.
// This is only a workaround
self.ldisc
.push_char(ch, |content| early_print!("{}", content))
self.ldisc.push_char(
ch,
|signum| {
if let Some(foreground) = self.job_control.foreground() {
broadcast_signal_async(Arc::downgrade(&foreground), signum);
}
},
|content| early_print!("{}", content),
)
}
}
@ -156,25 +163,6 @@ impl Device for Tty {
}
}
pub fn new_job_control_and_ldisc() -> (Arc<JobControl>, Arc<LineDiscipline>) {
let job_control = Arc::new(JobControl::new());
let send_signal = {
let cloned_job_control = job_control.clone();
move |signal: KernelSignal| {
let Some(foreground) = cloned_job_control.foreground() else {
return;
};
foreground.broadcast_signal(signal);
}
};
let ldisc = LineDiscipline::new(Arc::new(send_signal));
(job_control, ldisc)
}
pub fn get_n_tty() -> &'static Arc<Tty> {
N_TTY.get().unwrap()
}

View File

@ -23,8 +23,8 @@ pub use clone::{clone_child, CloneArgs, CloneFlags};
pub use credentials::{Credentials, Gid, Uid};
pub use kill::{kill, kill_all, kill_group, tgkill};
pub use process::{
enqueue_signal_async, spawn_init_process, ExitCode, JobControl, Pgid, Pid, Process,
ProcessGroup, Session, Sid, Terminal,
broadcast_signal_async, enqueue_signal_async, spawn_init_process, ExitCode, JobControl, Pgid,
Pid, Process, ProcessGroup, Session, Sid, Terminal,
};
pub use process_filter::ProcessFilter;
pub use process_vm::{

View File

@ -725,3 +725,23 @@ pub fn enqueue_signal_async(process: Weak<Process>, signum: SigNum) {
work_queue::WorkPriority::High,
);
}
/// Broadcasts a process-directed kernel signal asynchronously.
///
/// This is the asynchronous version of [`ProcessGroup::broadcast_signal`]. By asynchronous, this
/// method submits a work item and returns, so this method doesn't sleep and can be used in atomic
/// mode.
pub fn broadcast_signal_async(process_group: Weak<ProcessGroup>, signal: SigNum) {
use super::signal::signals::kernel::KernelSignal;
use crate::thread::work_queue;
let signal = KernelSignal::new(signal);
work_queue::submit_work_func(
move || {
if let Some(process_group) = process_group.upgrade() {
process_group.broadcast_signal(signal);
}
},
work_queue::WorkPriority::High,
);
}