Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion BUGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,7 @@ The heap picks GFP_ATOMIC by itself under a spin lock, so an allocation
there takes the atomic reserve silently (Linux needs it named, and
might_alloc reports a GFP_KERNEL one in atomic context). A LOCKDEP boot
lists each lock allocated under once (`LOCKDEP:ATOMIC_ALLOC ... under
<lock> (taken at ...)`; the shutdown summary counts them): 109 locks,
<lock> (taken at ...)`; the shutdown summary counts them): 105 locks,
mostly collections grown under spin mutexes, in fs, net, task and
ipc (procfs: only its mount-time tree building). Each wants its allocation moved out of the
lock (preallocate, or a sleeping lock as in Linux).
Expand Down
8 changes: 8 additions & 0 deletions doc/STYLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ The rules for `unsafe` are in `doc/UNSAFE.md` and the locking rules in
built up over time. Never `vec![x; n]`, `with_capacity`, `push`,
`extend`, `resize` or `reserve` on a length the kernel does not bound
itself. A stream of bytes is a queue of bounded chunks, not one buffer.
- **Allocate once, outside locks.** Under any spinlock or in softirq an
allocation cannot reclaim or fall back to vmalloc, so order 4 (64 KiB)
and up can fail under fragmentation, and an infallible one then panics.
When a buffer's size is known or tightly bounded and it is filled at
once (above all under a lock or in a callback that runs under one), size
it before taking the lock: one allocation, no allocator work inside.
Don't pre-size long-lived, slowly growing tables or usually-empty lists,
and `collect()` from an exact-size iterator already allocates once.
- **Types carry meaning.** Use newtypes for addresses and numbers that
must not be mixed up (`PhysAddr`, `VirtAddr`, `Pfn`, device numbers,
fds), `bitflags` for flag words, and enums for closed sets.
Expand Down
119 changes: 0 additions & 119 deletions doc/res/hk-with-capacity.md

This file was deleted.

4 changes: 3 additions & 1 deletion fs/dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,9 @@ fn getdents(fd: i32, dirp: u64, count: u64, fmt: Fmt) -> i64 {

let _pos = file.pos_lock();
let limit = count.min(GETDENTS_CHUNK);
let mut buf = Vec::new();
// Sized up front: the actor runs under the directory locks, where
// growing the buffer would be an atomic allocation
let mut buf = Vec::with_capacity(limit);
let mut full = false;
let mut actor = |e: &DirEntry, d_off: u64| {
full = !put_dirent(&mut buf, limit, e, d_off, fmt);
Expand Down
46 changes: 32 additions & 14 deletions fs/namei.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,13 +206,11 @@ fn walk(
Some(p) if !path.starts_with(b"/") => p,
_ => root.clone(),
};
let comps: Vec<&[u8]> = path
.split(|&c| c == b'/')
.filter(|s| !s.is_empty())
.collect();
for (i, name) in comps.iter().enumerate() {
let mut next = next_comp(path);
while let Some((name, rest)) = next {
next = next_comp(rest);
may_lookup(c, &cur.dentry)?;
match *name {
match name {
b"." => continue,
b".." => {
cur = follow_dotdot(cur, &root);
Expand All @@ -226,9 +224,9 @@ fn walk(
} else {
follow_mount(child)
};
let last = i + 1 == comps.len();
let last = next.is_none();
if (!last || flags.follow) && child.get_inode().is_some_and(|n| n.mode().is_symlink()) {
return follow_link(c, &cur, &child.dentry, &comps[i + 1..], flags, depth);
return follow_link(c, &cur, &child.dentry, rest, flags, depth);
}
cur = child;
}
Expand All @@ -238,6 +236,15 @@ fn walk(
Ok(cur)
}

/// The next component of `p` and what follows it, skipping slashes
/// (no allocation: the walk steps through the path in place)
fn next_comp(p: &[u8]) -> Option<(&[u8], &[u8])> {
let start = p.iter().position(|&c| c != b'/')?;
let p = &p[start..];
let end = p.iter().position(|&c| c == b'/').unwrap_or(p.len());
Some(p.split_at(end))
}

/// The root absolute paths start at and ".." stops at (Linux nd->root):
/// the task's fs root (chroot), else, for boot code with no fs yet,
/// where "/" lands in the namespace (the rootfs, not its nullfs root)
Expand Down Expand Up @@ -278,7 +285,7 @@ fn follow_link(
c: &Cred,
dir: &Path,
link: &Dentry,
rest: &[&[u8]],
rest: &[u8],
flags: LookupFlags,
depth: usize,
) -> Result<Path, KernelError> {
Expand All @@ -298,18 +305,16 @@ fn follow_link(
if let Some(p) = inode.i_op.jump_link(&inode, &dir.mnt) {
let p = p?;
inode.touch_atime(&dir.mnt);
if !rest.is_empty() || flags.directory {
if next_comp(rest).is_some() || flags.directory {
return Err(KernelError::NotDirectory);
}
return Ok(p);
}
let target = inode.i_op.readlink(&inode)?;
inode.touch_atime(&dir.mnt);
// `rest` is empty or starts at a slash: the walk goes on through it
let mut path = target;
for comp in rest {
path.push(b'/');
path.extend_from_slice(comp);
}
path.extend_from_slice(rest);
walk(c, Some(dir.clone()), &path, flags, depth + 1)
}

Expand Down Expand Up @@ -617,3 +622,16 @@ pub fn filename_create(dirfd: i32, path: &[u8], want_dir: bool) -> Result<Parent
}
Ok(p)
}

#[cfg(test)]
mod tests {
use super::next_comp;

#[test]
fn components() {
assert_eq!(next_comp(b"//a//b/"), Some((&b"a"[..], &b"//b/"[..])));
assert_eq!(next_comp(b"//b/"), Some((&b"b"[..], &b"/"[..])));
assert_eq!(next_comp(b"/"), None);
assert_eq!(next_comp(b""), None);
}
}
39 changes: 36 additions & 3 deletions fs/poll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,14 @@ fn do_poll_internal(fds: u64, nfds: u32, deadline: Deadline) -> i64 {
return KernelError::BadAddress.sysret();
}

// Create poll context (wait queue registrations, dropped at return)
let mut ctx = PollContext::new(tid);
// Create poll context (wait queue registrations, dropped at return;
// only a poll that may sleep registers)
let regs = if deadline == Some(0) {
0
} else {
pollfds.iter().filter(|p| p.fd >= 0).count()
};
let mut ctx = PollContext::sized(tid, regs);
let mut first_pass = true;
let mut ready_count;

Expand Down Expand Up @@ -251,6 +257,11 @@ fn do_poll_internal(fds: u64, nfds: u32, deadline: Deadline) -> i64 {
set_current_state(SleepState::Running);
break;
}
// Nothing ready and a registration failed (Linux do_poll)
if let Some(e) = ctx.error() {
set_current_state(SleepState::Running);
return e.sysret();
}

match poll_sleep(deadline) {
Some(0) => break,
Expand Down Expand Up @@ -483,7 +494,24 @@ fn do_select_internal(
};
let bit = |v: &[u64], fd: usize| v.get(fd / 64).is_some_and(|w| w & (1 << (fd % 64)) != 0);

let mut ctx = PollContext::new(current_tid());
// Sized for the fds asked about (not nfds: select(FD_SETSIZE) with a
// few bits set is the common case); only a select that may sleep
// registers
let asked = || {
// Bits at or past n in the last word are not polled
let mask = |i: usize| {
if (i + 1) * 64 > n {
(1u64 << (n % 64)) - 1
} else {
!0
}
};
(0..words)
.map(|i| ((rd[i] | wr[i] | ex[i]) & mask(i)).count_ones() as usize)
.sum::<usize>()
};
let regs = if deadline == Some(0) { 0 } else { asked() };
let mut ctx = PollContext::sized(current_tid(), regs);
let mut first_pass = true;
let mut ready_count;
let fd_table = current_fd_table();
Expand Down Expand Up @@ -545,6 +573,11 @@ fn do_select_internal(
set_current_state(SleepState::Running);
break;
}
// Nothing ready and a registration failed (Linux do_select)
if let Some(e) = ctx.error() {
set_current_state(SleepState::Running);
return e.sysret();
}
match poll_sleep(deadline) {
Some(0) => break,
Some(r) => return r,
Expand Down
12 changes: 9 additions & 3 deletions fs/proc/mounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ const ESC: &[u8] = b" \t\n\\";
/// For source and type, '#' too (Linux mangle in proc_namespace.c)
const ESC_HASH: &[u8] = b" \t\n\\#";

/// Typical line lengths, to size a whole file in one allocation (a
/// longer line only grows the buffer)
const MOUNTS_LINE: usize = 96;
const MOUNTINFO_LINE: usize = 128;

/// The mounts task `t` sees: (mount, mountpoint as seen from its root),
/// by mount order; a mount its root does not reach is left out
fn visible(t: &TaskRef) -> Vec<(Arc<Mount>, Vec<u8>)> {
Expand Down Expand Up @@ -75,8 +80,9 @@ fn mnt_opts(out: &mut Vec<u8>, m: &Mount) {
/// /proc/<pid>/mounts (Linux show_vfsmnt): source mountpoint type
/// options 0 0
pub fn show_mounts(t: &TaskRef) -> Vec<u8> {
let mut out = Vec::new();
for (m, mp) in visible(t) {
let vis = visible(t);
let mut out = Vec::with_capacity(vis.len() * MOUNTS_LINE);
for (m, mp) in vis {
mangle(&mut out, &m.devname, ESC_HASH);
out.push(b' ');
mangle(&mut out, &mp, ESC);
Expand All @@ -98,8 +104,8 @@ pub fn show_mounts(t: &TaskRef) -> Vec<u8> {
/// root mountpoint options [optional fields] - type source super-options
pub fn show_mountinfo(t: &TaskRef) -> Vec<u8> {
use alloc::fmt::Write;
let mut out = Vec::new();
let vis = visible(t);
let mut out = Vec::with_capacity(vis.len() * MOUNTINFO_LINE);
for (m, mp) in vis.iter().cloned() {
// The namespace root's parent is itself (Linux: its mnt_parent)
let parent = m.get_parent().map_or(m.mnt_id, |p| p.mnt_id);
Expand Down
Loading