*BSD News Article 32140


Return to BSD News archive

Path: sserve!newshost.anu.edu.au!harbinger.cc.monash.edu.au!bunyip.cc.uq.oz.au!munnari.oz.au!comp.vuw.ac.nz!newshost.wcc.govt.nz!aladdin.wcc.govt.nz!zheng
From: zheng@aladdin.wcc.govt.nz ()
Newsgroups: comp.os.386bsd.questions
Subject: netbsd-0.9 syscall -- LONG !!! --
Date: 23 Jun 1994 10:54:17 GMT
Organization: Wellington City Council, Wellington, New Zealand
Lines: 213
Sender: Chuck Zheng (zheng@aladdin.wcc.govt.nz)
Message-ID: <2ubpkp$ccg@golem.wcc.govt.nz>
NNTP-Posting-Host: aladdin.wcc.govt.nz

Hello,

I have always been wondering how does a user process trap into kernel
via a system call on NetBSD (v0.9).  After on and off efforts over
several months study of NetBSD-0.9 source codes, Bill Jolitzs' DDJ
series and a 386 programmer guide,  together advises from serveral
poeple on the net,  I finally sort of patch the whole picture to a
recognizable form, as listed bellow.  I still have not found where/how 
is user process LDT set up exactly.  I would like to read your comments
and advise.


cheers,
chuck


How does netbsd-0.9 do system call
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

- user process issues intersegmental (far procedure) call: [DDJ,Jan91,p38]

	lcall	$0x7,	0x0	# [DDJ,Jan91,p39,Fig8]

  which saves current code segment selector (value of CS) and PC (EIP) value
  on stack. control then transfers to the destination specified by the
  operand: (selector-16bit : offset-32bit) pair (0x7, 0x0). In other words,
  selector 0x7 is loaded into CS register. [386/486,p187]

- Actually, to work around of a bug in GAS, user process issues a macro: 

	LCALL(0x7,0x0)

  this can be expanded to

	.byte 0x9a ; .long 0x0; .word 0x7

  which equals to 

	lcall	$0x7,	0x0	

  ## info source came from [DDJ,Mar91,p82]


- This 16-bit selector 0x7 = 0000 0000 0000 0111 (in binary) [386/486,p59]
			     ----------------A--
				Index = 0    | RPL = 3 = user mode/level
					     |
					     TI = 1 = Local Descriptor Table

  Thus the selector points to the slot zero (the first entry) of the
  current LDT [386/486,p58].  

- First entry for *KERNEL* LDT is set up in file /sys/arch/i386/i386/machdep.c 

  ...
  #define GCODE_SEL       1       /* Kernel Code Descriptor */
  #define GLDT_SEL        3       /* LDT - eventually one per process */

  ...
  /* local descriptor table */
  union descriptor ldt[5];
  #define LSYS5CALLS_SEL  0       /* forced by intel BCS */
  ...

  init386(first)
  {
  ...
  struct gate_descriptor *gdp;

  ...
  lldt(GSEL(GLDT_SEL, SEL_KPL));

  ...
  /* make a call gate to reenter kernel with */
  gdp = &ldt[LSYS5CALLS_SEL].gd;

  x = (int) &IDTVEC(syscall);
  gdp->gd_looffset = x++;
  gdp->gd_selector = GSEL(GCODE_SEL,SEL_KPL);
  gdp->gd_stkcpy = 0;
  gdp->gd_type = SDT_SYS386CGT;
  gdp->gd_dpl = SEL_UPL;
  gdp->gd_p = 1;
  gdp->gd_hioffset = ((int) &IDTVEC(syscall)) >>16;

  ...
  }

  macros and struct are defined in /sys/arch/i386/include/segments.h:

  #define SEL_KPL 0               /* kernel priority level */     
  #define SEL_UPL 3               /* user priority level */
  #define GSEL(s,r)       (((s)<<3) | r)         /* a global selector */
  ...
  #define SDT_SYS386CGT   12      /* system 386 call gate */

  lldt() is defined in /sys/arch/i386/include/locore.s

  #define ENTRY(name)     .globl _/**/name; ALIGN_TEXT; _/**/name:

  ...
    /*
    * void lldt(u_short sel)
    */
  ENTRY(lldt)
    lldt    4(%esp)
    ret


  init386() is invoked during system startup by /sys/arch/i386/i386/locore.s.
  It sets up the first entry of LDT as a call gate descriptor, with selector

	GSEL(GCODE_SEL,SEL_KPL)

  points to a kernel code segment and an offset into the segment. [386/486,p112]
  The offset is the kernel space address for interrupt descriptor table IDT
  entry:

	&IDTVEC(syscall)

  which is defined in /sys/arch/i386/i386/locore.s. (some macros in locore.s
  and machdep.c interprets IDTVEC).

- The setup for kernal LDT is copied/inherited by subsequent user process 
  during fork().  /sys/kern/kern_fork.c reads:

  int fork1(p1, isvfork, retval)
    register struct proc *p1;
    int isvfork, retval[];
  {
    register struct proc *p2;

    ...
    /* Allocate new proc. */
    MALLOC(p2, struct proc *, sizeof(struct proc), M_PROC, M_WAITOK);

    ...   
    /*
    * Make a proc table entry for the new process.
    * Start by zeroing the section of proc that is zero-initialized,
    * then copy the section that is copied directly from the parent.
    */
    bzero(&p2->p_startzero,
      (unsigned) ((caddr_t)&p2->p_endzero - (caddr_t)&p2->p_startzero));
    bcopy(&p1->p_startcopy, &p2->p_startcopy,
      (unsigned) ((caddr_t)&p2->p_endcopy - (caddr_t)&p2->p_startcopy));

    ...
  }

  struct proc is defined in /usr/src/sys/sys/proc.h.  A proc slot has a pointer
  to struct user:

	struct  user *p_addr;   /* kernel virtual addr of u-area (PROC ONLY) */

  struct user is defined in /usr/src/sys/sys/user.h.  user has a field:

	struct  pcb u_pcb;

  struct pcb is defined in /sys/arch/i386/include/pcb.h.  pcb has a field:

	struct  i386tss pcb_tss;

  struct i386tss is defined in /sys/arch/i386/include/tss.h.  i386tss has a
  field:

	int     tss_ldt;        /* actually 16 bits: top 16 bits must be zero*/

  This field holds the selector for LDTR.


  Since bcopy only copys "struct user *p_addr" from p1 to p2, I guess XXX
  some routhines overlooked by me must do the actual "copy" of tss_ldt from
  p1 to p2, or there is a copy-on-write scheme implemented (is that vfork?).

  Can somebody clarify this further (and point out error I have made above)?
  There is also a cpu_fork() routine in /sys/arch/i386/i386/vm_machdep.c,
  which does some copying similar to fork1():

  cpu_fork(p1, p2)
    register struct proc *p1, *p2;
  {
    ...
    /*
     * Copy pcb and stack from proc p1 to p2. 
     * We do this as cheaply as possible, copying only the active
     * part of the stack.  The stack and pcb need to agree;
     * this is tricky, as the final pcb is constructed by savectx,
     * but its frame isn't yet on the stack when the stack is copied.
     * swtch compensates for this when the child eventually runs.
     * This should be done differently, with a single call
     * that copies and updates the pcb+stack,
     * replacing the bcopy and savectx.
     */
    p2->p_addr->u_pcb = p1->p_addr->u_pcb;
    offset = mvesp() - (int)kstack;
    bcopy((caddr_t)kstack + offset, (caddr_t)p2->p_addr + offset,
      (unsigned) ctob(UPAGES) - offset);
    p2->p_regs = p1->p_regs;

  ...
  }

  I have not been able to find out who calls this routine.  Does anyone
  knows what is it for and how is it used?


Reference
~~~~~~~~~
[DDJ] -- Dr. Dobb's Journal

[386/486] -- Microsoft's 80386/80486 Programming Guide.  Ross Nelson.