A thread is a struct proc that shares three counted objects with others of
its group: the address space (struct mm: page tables, heap top, VMAs and a
lock for changing them), the descriptor table, and the signal dispositions.
clone() shares what its flags say and fork() copies.

- clone(): pthread_create's shape (VM, FILES, SIGHAND, THREAD, SETTLS,
  PARENT_SETTID, CHILD_CLEARTID) and posix_spawn's (VM, VFORK, a stack).
- futex: WAIT, WAIT_BITSET with timeouts, WAKE, REQUEUE, CMP_REQUEUE, keyed
  by the word's kernel address so shared pages meet. set_tid_address keeps
  the word that exit zeroes and wakes, for pthread_join.
- exit ends a thread; exit_group, a fatal signal, or the leader exiting ends
  them all. reapd frees exited threads; the leader is reaped by its parent
  once the rest are gone. getpid is the group, gettid the thread.
- Stops and resumes reach every thread; other process signals the leader.
- exec kills the other threads and unshares the dispositions.
- No TLB shootdown, so pages unmapped from a shared address space are freed
  only once every CPU has reloaded CR3 or is idle.
- procfs: /proc lists processes, task/ lists threads, comm, Threads:, Tgid.
- The console honours VMIN 0 / VTIME (btop's input drain hung on it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
Nexoniarz 2026-10-25 03:03:50 +01:00
commit f4d7f9100d
19 changed files with 1300 additions and 248 deletions

View file

@ -143,7 +143,7 @@ argfd(int n, int *pfd, struct file **pf)
struct file *f;
if (argint(n, &fd) < 0)
return -1;
if (fd < 0 || fd >= NOFILE || !(f = myproc()->ofile[fd]))
if (fd < 0 || fd >= NOFILE || !(f = myproc()->files->ofile[fd]))
return -1;
if (pfd) *pfd = fd;
if (pf) *pf = f;
@ -155,13 +155,18 @@ argfd(int n, int *pfd, struct file **pf)
int
fdalloc(struct file *f)
{
// Locked: threads share the table, and two picking the same empty slot
// would each believe they owned it.
files_lock();
for (int fd = 0; fd < NOFILE; fd++) {
if (!myproc()->ofile[fd]) {
myproc()->ofile[fd] = f;
myproc()->ofile_flags[fd] = 0;
if (!myproc()->files->ofile[fd]) {
myproc()->files->ofile[fd] = f;
myproc()->files->flags[fd] = 0;
files_unlock();
return fd;
}
}
files_unlock();
return -1;
}
@ -178,7 +183,7 @@ file_setpath(struct file *f, int dirfd, const char *path)
struct file *d;
if (dirfd == AT_FDCWD)
base = myproc()->cwd_path;
else if (dirfd >= 0 && dirfd < NOFILE && (d = myproc()->ofile[dirfd]) && d->path)
else if (dirfd >= 0 && dirfd < NOFILE && (d = myproc()->files->ofile[dirfd]) && d->path)
base = d->path;
else
return;
@ -218,8 +223,8 @@ static struct inode *
at_dir(int dirfd)
{
if (dirfd == AT_FDCWD) return myproc()->cwd;
if (dirfd < 0 || dirfd >= NOFILE || !myproc()->ofile[dirfd]) return 0;
struct file *f = myproc()->ofile[dirfd];
if (dirfd < 0 || dirfd >= NOFILE || !myproc()->files->ofile[dirfd]) return 0;
struct file *f = myproc()->files->ofile[dirfd];
if (f->type != FD_INODE) return 0;
return f->ip;
}
@ -393,12 +398,12 @@ addr_t sys_dup2(void)
int oldfd, newfd;
struct file *f;
if (argint(0, &oldfd) < 0 || argint(1, &newfd) < 0) return -1;
if (oldfd < 0 || oldfd >= NOFILE || !(f = myproc()->ofile[oldfd])) return -EBADF;
if (oldfd < 0 || oldfd >= NOFILE || !(f = myproc()->files->ofile[oldfd])) return -EBADF;
if (newfd < 0 || newfd >= NOFILE) return -EBADF;
if (oldfd == newfd) return newfd;
if (myproc()->ofile[newfd]) fileclose(myproc()->ofile[newfd]);
myproc()->ofile[newfd] = filedup(f);
myproc()->ofile_flags[newfd] = 0; // dup2 never carries FD_CLOEXEC over
if (myproc()->files->ofile[newfd]) fileclose(myproc()->files->ofile[newfd]);
myproc()->files->ofile[newfd] = filedup(f);
myproc()->files->flags[newfd] = 0; // dup2 never carries FD_CLOEXEC over
return newfd;
}
@ -412,9 +417,9 @@ addr_t sys_dup3(void)
// Unlike dup2, equal fds are an error (dup3(2)). Without this check we
// would fileclose(f) and then filedup() the file we just released.
if (oldfd == newfd) return -EINVAL;
if (myproc()->ofile[newfd]) fileclose(myproc()->ofile[newfd]);
myproc()->ofile[newfd] = filedup(f);
myproc()->ofile_flags[newfd] = 0;
if (myproc()->files->ofile[newfd]) fileclose(myproc()->files->ofile[newfd]);
myproc()->files->ofile[newfd] = filedup(f);
myproc()->files->flags[newfd] = 0;
return newfd;
}
@ -423,8 +428,8 @@ addr_t sys_close(void)
int fd;
struct file *f;
if (argfd(0, &fd, &f) < 0) return -EBADF;
myproc()->ofile[fd] = 0;
myproc()->ofile_flags[fd] = 0;
myproc()->files->ofile[fd] = 0;
myproc()->files->flags[fd] = 0;
lock_close_fd(f);
fileclose(f);
return 0;
@ -444,23 +449,23 @@ sys_fcntl(void)
{
int fd, cmd, arg;
if (argint(0, &fd) < 0 || argint(1, &cmd) < 0 || argint(2, &arg) < 0) return -1;
if (fd < 0 || fd >= NOFILE || !myproc()->ofile[fd]) return -1;
struct file *f = myproc()->ofile[fd];
if (fd < 0 || fd >= NOFILE || !myproc()->files->ofile[fd]) return -1;
struct file *f = myproc()->files->ofile[fd];
switch (cmd) {
case F_DUPFD:
case F_DUPFD_CLOEXEC: {
if (arg < 0) return -EINVAL;
for (int nfd = arg; nfd < NOFILE; nfd++) {
if (!myproc()->ofile[nfd]) {
myproc()->ofile[nfd] = filedup(f);
myproc()->ofile_flags[nfd] = (cmd == F_DUPFD_CLOEXEC) ? FD_CLOEXEC : 0;
if (!myproc()->files->ofile[nfd]) {
myproc()->files->ofile[nfd] = filedup(f);
myproc()->files->flags[nfd] = (cmd == F_DUPFD_CLOEXEC) ? FD_CLOEXEC : 0;
return nfd;
}
}
return -EMFILE;
}
case F_GETFD: return myproc()->ofile_flags[fd];
case F_SETFD: myproc()->ofile_flags[fd] = arg; return 0;
case F_GETFD: return myproc()->files->flags[fd];
case F_SETFD: myproc()->files->flags[fd] = arg; return 0;
case F_GETFL: {
int flags = 0;
if (f->readable && f->writable) flags = O_RDWR;
@ -592,7 +597,7 @@ addr_t sys_pipe(void)
int fd0 = -1, fd1;
if (argptr(0, (void*)&fd, 2*sizeof(fd[0])) < 0 || pipealloc(&rf, &wf) < 0) return -1;
if ((fd0 = fdalloc(rf)) < 0 || (fd1 = fdalloc(wf)) < 0) {
if (fd0 >= 0) myproc()->ofile[fd0] = 0;
if (fd0 >= 0) myproc()->files->ofile[fd0] = 0;
fileclose(rf); fileclose(wf);
return -1;
}
@ -614,14 +619,14 @@ addr_t sys_pipe2(void)
if (!fds || pipealloc(&rf, &wf) < 0) return -1;
fd0 = fd1 = -1;
if ((fd0 = fdalloc(rf)) < 0 || (fd1 = fdalloc(wf)) < 0) {
if (fd0 >= 0) myproc()->ofile[fd0] = 0;
if (fd0 >= 0) myproc()->files->ofile[fd0] = 0;
fileclose(rf); fileclose(wf);
return -1;
}
rf->nonblock = wf->nonblock = (flags & O_NONBLOCK) != 0;
if (flags & O_CLOEXEC) {
myproc()->ofile_flags[fd0] |= FD_CLOEXEC;
myproc()->ofile_flags[fd1] |= FD_CLOEXEC;
myproc()->files->flags[fd0] |= FD_CLOEXEC;
myproc()->files->flags[fd1] |= FD_CLOEXEC;
}
fds[0] = fd0; fds[1] = fd1;
return 0;
@ -1518,7 +1523,7 @@ addr_t sys_ioctl(void)
if (f->type != FD_INODE || !f->ip || !f->writable)
return -EBADF;
if (sfd < 0 || sfd >= NOFILE || (sf = pr->ofile[sfd]) == 0)
if (sfd < 0 || sfd >= NOFILE || (sf = pr->files->ofile[sfd]) == 0)
return -EBADF;
if (sf->type != FD_INODE || !sf->ip || !sf->readable)
return -EBADF;
@ -2344,8 +2349,8 @@ addr_t sys_fstatat(void)
// AT_EMPTY_PATH: if path is empty, fstat the dirfd itself.
if ((flags & AT_EMPTY_PATH) && path[0] == '\0') {
if (dirfd < 0 || dirfd >= NOFILE || !myproc()->ofile[dirfd]) return -1;
struct file *f = myproc()->ofile[dirfd];
if (dirfd < 0 || dirfd >= NOFILE || !myproc()->files->ofile[dirfd]) return -1;
struct file *f = myproc()->files->ofile[dirfd];
if (f->type != FD_INODE) return -1;
ilock(f->ip);
if (myproc()->syscall_map) stati_linux(f->ip, (struct linux_stat*)st);