Threads
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:
parent
2372806862
commit
f4d7f9100d
19 changed files with 1300 additions and 248 deletions
|
|
@ -369,7 +369,8 @@ exec_elf(char *path, struct argsrc *av, struct argsrc *ev)
|
|||
uint64 sz, sp;
|
||||
struct elfhdr elf;
|
||||
struct inode *ip;
|
||||
pml4e_t *pgdir, *oldpgdir;
|
||||
pml4e_t *pgdir;
|
||||
struct mm *oldmm, *newmm;
|
||||
|
||||
// The arguments are measured before anything is loaded, so that too many
|
||||
// of them is E2BIG with nothing to undo -- and so the stack can be sized to
|
||||
|
|
@ -540,10 +541,10 @@ exec_elf(char *path, struct argsrc *av, struct argsrc *ev)
|
|||
safestrcpy(myproc()->exe, path, sizeof(myproc()->exe));
|
||||
|
||||
for(int i = 0; i < NOFILE; i++){
|
||||
if(myproc()->ofile[i] && (myproc()->ofile_flags[i] & FD_CLOEXEC)){
|
||||
fileclose(myproc()->ofile[i]);
|
||||
myproc()->ofile[i] = 0;
|
||||
myproc()->ofile_flags[i] = 0;
|
||||
if(myproc()->files->ofile[i] && (myproc()->files->flags[i] & FD_CLOEXEC)){
|
||||
fileclose(myproc()->files->ofile[i]);
|
||||
myproc()->files->ofile[i] = 0;
|
||||
myproc()->files->flags[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -557,18 +558,19 @@ exec_elf(char *path, struct argsrc *av, struct argsrc *ev)
|
|||
myproc()->map_size = 0;
|
||||
}
|
||||
|
||||
// Detach shared segments before anything forgets which mappings they were.
|
||||
//
|
||||
// This has to happen while myproc()->pgdir and myproc()->vma[] still describe the
|
||||
// *old* image: the memset below erases the bookkeeping, and freevm() at the
|
||||
// end would then free a shared memory segment's pages as though they were
|
||||
// ordinary heap. Doing it here rather than there is the difference between
|
||||
// detaching a segment and corrupting whoever else has it mapped.
|
||||
proc_release_shared(myproc());
|
||||
|
||||
oldpgdir = myproc()->pgdir;
|
||||
// A new image is a new address space: a fresh mm, and the old one dropped
|
||||
// at the end. mm_put() detaches shared segments before freeing anything,
|
||||
// and does nothing at all while a vfork parent is still using it.
|
||||
if ((newmm = mm_alloc()) == 0)
|
||||
goto bad;
|
||||
proc_exec_threads();
|
||||
newmm->pgdir = pgdir;
|
||||
newmm->sz = sz;
|
||||
oldmm = myproc()->mm;
|
||||
myproc()->mm = newmm;
|
||||
myproc()->pgdir = pgdir;
|
||||
myproc()->sz = sz;
|
||||
pgdir = 0; // newmm owns it now; not bad:'s to free
|
||||
|
||||
// New address space: reset all per-process mmap/signal/TLS state.
|
||||
// sig_handler/sig_restorer point into the *old* image — POSIX resets
|
||||
|
|
@ -579,26 +581,25 @@ exec_elf(char *path, struct argsrc *av, struct argsrc *ev)
|
|||
// Ignored signals stay ignored, and the mask is inherited (execve(2)):
|
||||
// neither points into the old image. `nohup` and every program that ignores
|
||||
// SIGPIPE before running a helper depend on it.
|
||||
memset(myproc()->vma, 0, sizeof(myproc()->vma));
|
||||
for (int i = 0; i < 32; i++)
|
||||
if (myproc()->sig_handler[i] != 1)
|
||||
myproc()->sig_handler[i] = 0;
|
||||
memset(myproc()->sig_restorer, 0, sizeof(myproc()->sig_restorer));
|
||||
if (myproc()->sighand->handler[i] != 1)
|
||||
myproc()->sighand->handler[i] = 0;
|
||||
memset(myproc()->sighand->restorer, 0, sizeof(myproc()->sighand->restorer));
|
||||
myproc()->fs_base = 0;
|
||||
myproc()->pending_signals = 0;
|
||||
myproc()->in_signal = 0;
|
||||
myproc()->sigsusp_active = 0;
|
||||
myproc()->mmap_next = 0;
|
||||
myproc()->mm->mmap_next = 0;
|
||||
|
||||
// The interpreter is ordinary memory to everything after this: fork copies
|
||||
// it, exit frees it. mmap carries on above it.
|
||||
if (interp[0]) {
|
||||
myproc()->vma[0].addr = INTERP_BASE;
|
||||
myproc()->vma[0].length = PGROUNDUP(interp_end);
|
||||
myproc()->vma[0].used = 1;
|
||||
myproc()->vma[0].no_free = 0;
|
||||
myproc()->vma[0].shmid = -1;
|
||||
myproc()->mmap_next = INTERP_BASE + PGROUNDUP(interp_end);
|
||||
myproc()->mm->vma[0].addr = INTERP_BASE;
|
||||
myproc()->mm->vma[0].length = PGROUNDUP(interp_end);
|
||||
myproc()->mm->vma[0].used = 1;
|
||||
myproc()->mm->vma[0].no_free = 0;
|
||||
myproc()->mm->vma[0].shmid = -1;
|
||||
myproc()->mm->mmap_next = INTERP_BASE + PGROUNDUP(interp_end);
|
||||
}
|
||||
|
||||
myproc()->tf->rax = 0;
|
||||
|
|
@ -637,7 +638,8 @@ exec_elf(char *path, struct argsrc *av, struct argsrc *ev)
|
|||
// old one's registers.
|
||||
memmove(myproc()->fpu, fpu_default, sizeof(myproc()->fpu));
|
||||
fxrstor(myproc()->fpu);
|
||||
freevm(oldpgdir);
|
||||
mm_put(oldmm);
|
||||
proc_exec_done(); // a vfork parent may have its memory back
|
||||
kfree((char*)scratch);
|
||||
kfree(bounce);
|
||||
fsstat.exec_cycles += rdtsc() - _t0;
|
||||
|
|
|
|||
|
|
@ -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_, "..");
|
||||
|
|
|
|||
Loading…
Reference in a new issue