procinit sets up the process table so the kernel can start managing processes.

void procinit(void) {
  struct proc *p;
  initlock(&pid_lock, "nextpid");
  initlock(&wait_lock, "wait_lock");
  for(p = proc; p < &proc[NPROC]; p++) {
      initlock(&p->lock, "proc");
      p->state = UNUSED;
      p->kstack = KSTACK((int) (p - proc));
  }
}

The process table

First, understand what it’s working with. xv6 has a fixed-size array of process structs:

struct proc proc[NPROC];

NPROC is 64. That’s it — xv6 can run at most 64 processes simultaneously. No dynamic allocation, no growing the table. This array is a global, so it lives in .bss and starts zeroed.

The two global locks

pid_lock protects the nextpid counter. Every time a new process is created, allocpid increments nextpid under this lock. Without it, two harts creating processes simultaneously could hand out the same PID.

wait_lock protects parent/child relationships. It’s used by kexit, kwait, and reparent — the places where processes inspect or modify p->parent. This needs its own lock because a parent calling kwait and a child calling kexit might run on different harts simultaneously, and both touch the parent pointer.

The per-process setup loop

For each of the 64 process slots, three things happen.

initlock(&p->lock, "proc") gives each process its own spinlock. This lock protects the process’s state, PID, and other fields. The scheduler acquires it before switching to a process, and various functions acquire it before modifying a process’s state.

p->state = UNUSED marks the slot as available. When allocproc needs to create a new process, it scans the table looking for an UNUSED slot.

p->kstack = KSTACK((int)(p - proc)) stores the virtual address of this process’s kernel stack. The actual physical pages were already allocated and mapped by proc_mapstacks during kvmmake. KSTACK is a macro that computes the virtual address based on the process’s index in the table. Each kernel stack is one page, with a guard page below it. procinit just records where each process’s stack lives so it can be used later when the process gets scheduled.

What it doesn’t do

It doesn’t create any processes. It doesn’t allocate page tables or trapframes. It just prepares the table — initializes locks, marks everything as available, and records kernel stack addresses. The first actual process gets created later by userinit.