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

@ -70,7 +70,8 @@
#define PF_SCHEDSTAT 6
#define PF_FD 7 // the directory /proc/<pid>/fd
#define PF_TASK 8 // the directory /proc/<pid>/task
#define PF_MAX 8
#define PF_COMM 9
#define PF_MAX 9
#define PF_FDENT 0x80 // | fd: one link in it
#define INO_PID(ino) ((int)((ino) >> 8))
@ -137,11 +138,13 @@ static const struct global_file pidfiles[] = {
// One symlink per open descriptor, named by its number and pointing at the
// path it was opened by, or at "socket:[ino]" and the like.
{ "fd", PF_FD },
// The process's threads, one directory each, named by thread id. A process
// here is one thread, so task/ holds a single entry, the pid, and it *is*
// /proc/<pid> -- the same inode. htop reads every process through it and
// The process's threads, one directory each, named by thread id; each is
// /proc/<tid> -- the same inode. htop reads every process through it and
// shows nothing at all without it.
{ "task", PF_TASK },
// The name alone, a line of it. btop reads this and nothing else for the
// Program column.
{ "comm", PF_COMM },
};
#define NPIDFILES ((int)(sizeof(pidfiles) / sizeof(pidfiles[0])))
@ -236,7 +239,9 @@ gen_pid_stat(struct sb *s, const struct procinfo *pi)
sbputc(s, ' '); sbputi(s, (int64)pi->cutime);
sbputc(s, ' '); sbputi(s, (int64)pi->cstime);
sbputs(s, " 20 0 1 0 0 "); // priority nice num_threads itrealvalue starttime
sbputs(s, " 20 0 "); // priority nice
sbputi(s, pi->nthreads); // 20 num_threads
sbputs(s, " 0 0 "); // itrealvalue starttime
sbputi(s, (int64)pi->sz); // 23 vsize, in bytes
sbputc(s, ' ');
sbputi(s, (int64)(pi->sz / PGSIZE)); // 24 rss, in pages
@ -248,7 +253,7 @@ gen_pid_status(struct sb *s, const struct procinfo *pi)
{
sbputs(s, "Name:\t"); sbputs(s, pi->name); sbputc(s, '\n');
sbputs(s, "State:\t"); sbputc(s, pi->stopped ? 'T' : state_char(pi->state));
sbputs(s, "\nTgid:\t"); sbputi(s, pi->pid);
sbputs(s, "\nTgid:\t"); sbputi(s, pi->tgid);
sbputs(s, "\nPid:\t"); sbputi(s, pi->pid);
sbputs(s, "\nPPid:\t"); sbputi(s, pi->ppid);
sbputs(s, "\nUid:\t"); sbputi(s, pi->uid);
@ -256,7 +261,7 @@ gen_pid_status(struct sb *s, const struct procinfo *pi)
sbputc(s, '\t'); sbputi(s, pi->uid);
sbputc(s, '\t'); sbputi(s, pi->uid);
sbputs(s, "\nVmSize:\t"); sbputi(s, (int64)(pi->sz / 1024));
sbputs(s, " kB\nThreads:\t1\n");
sbputs(s, " kB\nThreads:\t"); sbputi(s, pi->nthreads); sbputc(s, '\n');
// Where Linux keeps them, under the names Linux uses. The two are worth
// telling apart: a process with a high voluntary count is waiting on
@ -315,6 +320,7 @@ gen_file(uint64 ino, char *buf, int cap)
case PF_CMDLINE: gen_pid_cmdline(&s, &pi); break;
case PF_STATM: gen_pid_statm(&s, &pi); break;
case PF_EXE: sbputs(&s, pi.exe[0] ? pi.exe : pi.name); break;
case PF_COMM: sbputs(&s, pi.name); sbputc(&s, '\n'); break;
case PF_SCHEDSTAT:
sbputi(&s, (int64)pi.vruntime); sbputc(&s, ' ');
sbputi(&s, (int64)pi.nice); sbputc(&s, ' ');
@ -521,11 +527,13 @@ procfs_dirlookup(struct inode *dp, char *name, uint *poff)
INO_FILE(dp->inum) == PF_TASK ? (dp->inum & ~0xFFULL)
: PROCFS_ROOT);
// Inside /proc/<pid>/task/: the one thread, which is the process.
// Inside /proc/<pid>/task/: a thread of this process, which is /proc/<tid>.
if (dp->inum != PROCFS_ROOT && INO_FILE(dp->inum) == PF_TASK) {
if (name_to_pid(name) != INO_PID(dp->inum))
int tid = name_to_pid(name);
if (tid <= 0 || proc_snapshot_pid(tid, &pi) < 0 ||
pi.tgid != (int)INO_PID(dp->inum))
return 0;
return iget(dp->dev, (dp->inum & ~0xFFULL) | PF_DIR);
return iget(dp->dev, ((uint64)tid << 8) | PF_DIR);
}
// Inside /proc/<pid>/fd/: a number that is an open descriptor.
@ -647,7 +655,11 @@ procfs_getdents(struct inode *dp, char *out, int cap, uint64 *off)
EMIT(PROCFS_SELF, DT_LNK_, "self");
for (i = 0; i < NGLOBALS; i++)
EMIT(PROCFS_GLOBAL | globals[i].id, DT_REG_, globals[i].name);
// Processes only. A thread is reachable as /proc/<tid>, as on Linux,
// but listed under its process's task/ rather than here.
for (i = 0; proc_snapshot(i, &pi) == 0; i++) {
if (pi.tgid != pi.pid)
continue;
utoa((uint)pi.pid, name);
EMIT(((uint64)pi.pid << 8) | PF_DIR, DT_DIR_, name);
}
@ -664,8 +676,12 @@ procfs_getdents(struct inode *dp, char *out, int cap, uint64 *off)
} else if (INO_FILE(dp->inum) == PF_TASK) {
EMIT(dp->inum, DT_DIR_, ".");
EMIT(dp->inum & ~0xFFULL, DT_DIR_, "..");
utoa((uint)INO_PID(dp->inum), name);
EMIT((dp->inum & ~0xFFULL) | PF_DIR, DT_DIR_, name);
for (i = 0; proc_snapshot(i, &pi) == 0; i++) {
if (pi.tgid != (int)INO_PID(dp->inum))
continue;
utoa((uint)pi.pid, name);
EMIT(((uint64)pi.pid << 8) | PF_DIR, DT_DIR_, name);
}
} else {
EMIT(dp->inum, DT_DIR_, ".");
EMIT(PROCFS_ROOT, DT_DIR_, "..");