fs: inode and open-file tables that grow, with a hash for iget
The inode table was NINODE (200) entries, searched end to end on every
iget(), and a panic -- "iget: no inodes" -- when all of them were held.
Held means open, a cwd, a running binary or a directory mid-walk, so a
few busy processes reached it. iget() now finds an inode through a hash
on (dev, inum), takes a free one from a list, and makes more from a
slab cache once the NINODE in the image are all in use.
The open-file table had the same shape, NFILE (512) and then ENFILE,
and gets the same treatment: a free list, and the slab past NFILE.
/proc/fsstat's peaks now read against how many exist.
getrlimit(RLIMIT_NOFILE) said unlimited, which musl turns into
sysconf(_SC_OPEN_MAX) == -1, after which zsh believed it had no
descriptors and `exec {fd}<file` failed on the first one. It reports
NOFILE now. And a comment in sys_fs.c held a "*/" that ended it early.
Checked under QEMU: six processes holding 115 files each, 713 open
descriptions and 341 inodes at once, all back to the baseline after.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
parent
62d2a85ac8
commit
09e07423e8
6 changed files with 161 additions and 40 deletions
112
kernel/fs/fs.c
112
kernel/fs/fs.c
|
|
@ -45,6 +45,7 @@
|
|||
#include "file.h"
|
||||
#include "fat.h"
|
||||
#include "errno.h"
|
||||
#include "kmem.h"
|
||||
|
||||
extern uint uptime(void);
|
||||
|
||||
|
|
@ -215,17 +216,80 @@ bfree(int dev, uint b)
|
|||
// Inode table
|
||||
// ===========================================================================
|
||||
|
||||
// Every inode somebody holds, found by a hash on (dev, inum). One with no
|
||||
// reference goes on the free list and out of the hash, so a lookup never has
|
||||
// to skip it.
|
||||
//
|
||||
// This was NINODE entries searched end to end on every iget(), and "iget: no
|
||||
// inodes" -- a panic -- when all 200 were held. Held means open, a cwd, an
|
||||
// exec'd binary, or a directory some path walk is in the middle of, so 128
|
||||
// processes with a few files each reached it. The table now starts as those
|
||||
// NINODE and takes more from a slab cache when they are all held; it never
|
||||
// gives them back, which costs 280 bytes for each inode that was ever held at
|
||||
// once, and makes the panic mean "out of kernel memory".
|
||||
#define INODE_NHASH 4096 // a power of two
|
||||
|
||||
struct {
|
||||
struct spinlock lock;
|
||||
struct inode inode[NINODE];
|
||||
struct inode *hash[INODE_NHASH];
|
||||
struct inode *free; // through hnext, ref 0
|
||||
struct inode *all; // through anext
|
||||
struct kmem_cache *more;
|
||||
uint n;
|
||||
} itable;
|
||||
|
||||
static inline uint
|
||||
ihash(uint dev, uint64 inum)
|
||||
{
|
||||
uint64 h = (inum ^ ((uint64)dev << 40)) * 0x9E3779B97F4A7C15ULL;
|
||||
return (uint)(h >> 52) & (INODE_NHASH - 1);
|
||||
}
|
||||
|
||||
static void
|
||||
inode_new(struct inode *ip)
|
||||
{
|
||||
initsleeplock(&ip->lock, "inode");
|
||||
ip->anext = itable.all;
|
||||
itable.all = ip;
|
||||
ip->hnext = itable.free;
|
||||
itable.free = ip;
|
||||
itable.n++;
|
||||
}
|
||||
|
||||
void
|
||||
iinit(void)
|
||||
{
|
||||
initlock(&itable.lock, "itable");
|
||||
for (int i = 0; i < NINODE; i++)
|
||||
initsleeplock(&itable.inode[i].lock, "inode");
|
||||
for (int i = NINODE - 1; i >= 0; i--)
|
||||
inode_new(&itable.inode[i]);
|
||||
}
|
||||
|
||||
// How many inode slots exist, for /proc/fsstat.
|
||||
uint
|
||||
inode_slots(void)
|
||||
{
|
||||
return itable.n;
|
||||
}
|
||||
|
||||
// The held inode for (dev, inum), or 0. Under itable.lock.
|
||||
static struct inode *
|
||||
ifind(uint dev, uint64 inum)
|
||||
{
|
||||
for (struct inode *ip = itable.hash[ihash(dev, inum)]; ip; ip = ip->hnext)
|
||||
if (ip->dev == dev && ip->inum == inum)
|
||||
return ip;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
iunhash(struct inode *ip)
|
||||
{
|
||||
struct inode **pp = &itable.hash[ihash(ip->dev, ip->inum)];
|
||||
while (*pp && *pp != ip)
|
||||
pp = &(*pp)->hnext;
|
||||
if (*pp)
|
||||
*pp = ip->hnext;
|
||||
}
|
||||
|
||||
struct inode *iget(uint dev, uint64 inum);
|
||||
|
|
@ -450,20 +514,28 @@ iget(uint dev, uint64 inum)
|
|||
struct inode *ip, *empty;
|
||||
|
||||
acquire(&itable.lock);
|
||||
empty = 0;
|
||||
for (ip = &itable.inode[0]; ip < &itable.inode[NINODE]; ip++) {
|
||||
if (ip->ref > 0 && ip->dev == dev && ip->inum == inum) {
|
||||
ip->ref++;
|
||||
release(&itable.lock);
|
||||
return ip;
|
||||
}
|
||||
if (empty == 0 && ip->ref == 0)
|
||||
empty = ip;
|
||||
if ((ip = ifind(dev, inum)) != 0) {
|
||||
ip->ref++;
|
||||
release(&itable.lock);
|
||||
return ip;
|
||||
}
|
||||
|
||||
if ((empty = itable.free) != 0) {
|
||||
itable.free = empty->hnext;
|
||||
} else {
|
||||
// The slab allocator takes only its own lock and kalloc's.
|
||||
if (itable.more == 0)
|
||||
itable.more = kmem_cache_create("inode", sizeof(struct inode));
|
||||
if (itable.more == 0 || (empty = kmem_cache_alloc(itable.more)) == 0)
|
||||
panic("iget: no inodes");
|
||||
memset(empty, 0, sizeof *empty);
|
||||
inode_new(empty);
|
||||
itable.free = empty->hnext;
|
||||
}
|
||||
if (empty == 0)
|
||||
panic("iget: no inodes");
|
||||
|
||||
ip = empty;
|
||||
ip->hnext = itable.hash[ihash(dev, inum)];
|
||||
itable.hash[ihash(dev, inum)] = ip;
|
||||
ip->dev = dev;
|
||||
ip->inum = inum;
|
||||
ip->ref = 1;
|
||||
|
|
@ -481,11 +553,7 @@ iheld(uint dev, uint64 inum)
|
|||
{
|
||||
int held = 0;
|
||||
acquire(&itable.lock);
|
||||
for (struct inode *ip = &itable.inode[0]; ip < &itable.inode[NINODE]; ip++)
|
||||
if (ip->ref > 0 && ip->dev == dev && ip->inum == inum) {
|
||||
held = 1;
|
||||
break;
|
||||
}
|
||||
held = ifind(dev, inum) != 0;
|
||||
release(&itable.lock);
|
||||
return held;
|
||||
}
|
||||
|
|
@ -497,7 +565,7 @@ ibusy(uint dev, uint64 except)
|
|||
{
|
||||
int busy = 0;
|
||||
acquire(&itable.lock);
|
||||
for (struct inode *ip = &itable.inode[0]; ip < &itable.inode[NINODE]; ip++)
|
||||
for (struct inode *ip = itable.all; ip; ip = ip->anext)
|
||||
if (ip->ref > 0 && ip->dev == dev && ip->inum != except) {
|
||||
busy = 1;
|
||||
break;
|
||||
|
|
@ -584,8 +652,12 @@ iput(struct inode *ip)
|
|||
acquire(&itable.lock);
|
||||
}
|
||||
|
||||
if (--ip->ref == 0)
|
||||
if (--ip->ref == 0) {
|
||||
FSSTAT_DROP(fsstat.inode_used);
|
||||
iunhash(ip);
|
||||
ip->hnext = itable.free;
|
||||
itable.free = ip;
|
||||
}
|
||||
release(&itable.lock);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue