OS161 Flashcards

1
Q

runprogram

A

Called by cmd_progthread.

Actually doing the real job of setting up the process space.

Another task is loading the contents of the ELF file corresponding to the program.

In order tasks of runprogram:

  1. vfs open: Open a file using an abstraction that is machine independent
  2. create new address space
  3. Assigne the space to the new process
  4. Load the elf
  5. vfs close: close the vnode.
  6. allocates the third contiguos segment (the stack)
  7. enter_new_process -> context swtich to newly created process

code:

runprogram(char *progname)

{

struct addrspace *as;

struct vnode *v;

vaddr_t entrypoint, stackptr;

int result;

/* Open the file. */

result = vfs_open(progname, O_RDONLY, 0, &v);

if (result) {

return result;

}

/* We should be a new process. */

KASSERT(proc_getas() == NULL);

/* Create a new address space. */

as = as_create();

if (as == NULL) {

vfs_close(v);

return ENOMEM;

}

/* Switch to it and activate it. */

proc_setas(as);

as_activate();

/* Load the executable. */

result = load_elf(v, &entrypoint);

if (result) {

/* p_addrspace will go away when curproc is destroyed */

vfs_close(v);

return result;

}

/* Done with the file now. */

vfs_close(v);

/* Define the user stack in the address space */

result = as_define_stack(as, &stackptr);

if (result) {

/* p_addrspace will go away when curproc is destroyed */

return result;

}

/* Warp to user mode. */

enter_new_process(0 /*argc*/, NULL /*userspace addr of argv*/,

NULL /*userspace addr of environment*/,

stackptr, entrypoint);

/* enter_new_process does not return. */

panic(“enter_new_process returned\n”);

return EINVAL;

}

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

vnode structure

A

Top level file control block: reference counte, spinlo ck to access the count, pointer to filesystem that will contain a pointer to data and operation on the file systems.

IN VN_FS we can have emulated filesystems (fs of host machine), or simple filesystem (sfs).

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

How do we read from a file?

A

struct iovec iov;
struct uio ku;
struct addrspace *as;
Elf_Phdr ph; /* “Program header” = segment header */ off_t offset = …;

uio_kinit(&iov, &ku, &ph, sizeof(ph), offset, UIO_READ);

result = VOP_READ(v, &ku);

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

ELF files

A

ELF files contain address space segment descriptions, which are useful to the kernel when it is loading a new process.

Each ELF segment describes a contiguos region of the virtual address space.

For each segment, the ELF file has a segment image and a segment header, which describe:

  • the virtual address of the start of the segment
  • the length of the segment in the virtual addres space
  • the location of the start of the image in the elf file
  • the length of the image in the ELF file

Why is size replicated twice? The image is the exect copy of the binary, that may be smaller than the segment size, leaving the last parts of the segment zero-filled.

In OS161:

  • dumbvm implementation assumes that an ELF file contains two segments:
    • text, containing the program code and any read-only data
    • data segment, containing any other glabal program data
  • the elf file does not describe the stack, the stack has a fixed size
  • dumbvm creates a stack segment for each process, 12 pages long located at the end of the process address space.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Trap frame fields in syscall

A
  • tf_v0:
    • callno (syscall number)
    • the return value assigned to it at the end
  • tf_a0, tf_a1, tf_a2: syscall parameters (need to be caster
  • tf_a3: 0 if no errors occurred, 1 otherwise
  • tf_epc: program counter
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

How is syscall called?

A

tf_v0 is assigned a value by the assebly code:

  • then an exception is generated, that is handled by assembly level exception handler
  • that then calls mips_trap that checks if the generated trap has code corresponding to the one defined for syscalls
  • if so calls syscall passing to it tf

tf_v0 is then used in a switch to select which syscall to run.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

Multi-core system on OS161 why it wouldn’t work as a mutual exclusion mechanism to disable interrupts?

A
  • Because disabling interrputs on the currently used CPU wouldn’t stop other threads and other CPU.
  • Temporarily disabling interrupts on all the CPU wouldn’t work either, because other threads could be already running, potentially competing for shared resources
  • Disabling interrupts works only on single core platforms, where the number of running threads at a given time is just one.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

Spinlock in semaphore P, V

Why is the while used in P?

A
  • P: wait
  • V: signal

Spinlock is used to protect counter and because waiting channels need it to work.

P waits the counter to be != 0, wchan_sleep doens’t guaranteed that the condition will be true at the return from wchan_sleep, even if it was true when wchan_wakeone was called the condition was indeed true.

Multiple threads could be working on that same counter.

Threads are not guaranteed to be woken up in a FIFO fashion.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

wchan_sleep, wchan_wakeone

why spinlock?

wakeone wakes up more than one thread?

A
  • wchan sleep:
    • needs to release the spinlock before calling thread_switch
    • will acquire lock again after the thread has been brought back in execution
  • wchan_wakeone:
    • doesn’t need the lock, in os161 we just use it to check that the spinlock owner is the same that colled the the function
    • wakeone wakes up only one thread, in removes just the head from the linked list of waiting threads
    • wakeall puts all the threads in the ready queue in execution again
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

two versionso of sys_exit

A

v2:

sys_exit(int status) {
struct proc *p = curproc;
p->p_status = status; // save exit status in process
proc_remthread(p->curthread); // detach thread from process
lock_acquire(p->p_lock);
cv_signal(p->p_cv, p->p_lock); // signal termination of thread to sys_waitpid that is wating on the condition variable
lock_release(p->p_lock);
// no as_destroy -> proc_destroy will call it.
thread_exit(); // thread ends
}

v1:

sys_exit(status) {

struct addrspace *as = proc_getas();

as_destroy(as);

thread_exit();

panic(“thread_exit returned (should not happen)\n”);

(void) status;

}

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

argv argc

A
  • It is necessary to copy argv, argc parameters to user-memory because they are originally in kernel.
  • The copy should be created in user memory, more specifically in the beginning (high address) of the stack.
  • The origanl values are in kernel memory, not accessible to user programs, but even if the user process could access kernel memory a copy should me made anyway:
    • the thread could modify those value, thus to guarantee consistency we need to duplicate them.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

copyin, copyout

A

They are used to copy data between user and kernel memory.

Copyin has as destination the kernel mem, and copyout has as destination the user memory.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

Address space for the kernel

A

There are two possibilities:

  • kernel in physical space -> disable address translation for kernel
  • kernel in separate virtual address space -> need a way to change address
    • translation when moving between privileged and unprivileged code
  • OS161, Linux use third approach:
    • the kernel is mapped into a portion of the virtual address space of every process
    • memory protection mechanism is used to isolate kernel
    • advantage: application virtual addresses are easy to use for the kernel
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

Address translation

A

Dumb does this:

MIPS MMU tries to translate each virtual address using MMU entries

if page fault (address exception) ->

vm fault function handles exception: uses information from current proces addrspace to construct and load a TLB entry for the page.

On return from exception the MIPS retries the instruction that generated fault.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

Program loading in addrspace

A

An address space must be created for each new process, and load the program code inside the address space.

Program’s code and data are described in ELF (Executable and Linking Format), that is generated after compilation/linking.

the parameter of execv/execvp is the name of the ELF file.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

putch, getch

A
17
Q

sys_waitpid

A

int sys_waitpid(pid_t pid, userptr_t statusp, int options)
{
struct proc *p = proc_search_pid(pid);
int s;
if (p==NULL) return -1;
s = proc_wait(p);
if (statusp!=NULL)
return pid;

}

sys_waitpid

18
Q

proc_wait

A

int proc_wait(struct proc *proc) {
#if USE_SEMAPHORE_FOR_WAITPID
P(proc->p_sem);
#else
lock_acquire(proc->p_lock);
cv_wait(proc->p_cv);
lock_release(proc->p_lock);
#endif
return_status = proc->p_status;
proc_destroy(proc);
return return_status;

}

19
Q

sys_write

A

int
file_write(int fd, userptr_t buf_ptr, size_t size) {
struct iovec iov;
struct uio u;
int result, nwrite;
struct vnode *vn;
struct openfile *of;

if (fd<0||fd>OPEN_MAX) return -1;
of = curproc->fileTable[fd];
if (of==NULL) return -1;
vn = of->vn;
if (vn==NULL) return -1;

iov. iov_ubase = buf_ptr;
iov. iov_len = size;

u. uio_iov = &iov;
u. uio_iovcnt = 1;
u. uio_resid = size; // amount to read from the file
u. uio_offset = of->offset;
u. uio_segflg =UIO_USERISPACE;
u. uio_rw = UIO_WRITE;
u. uio_space = curproc->p_addrspace;

result = VOP_WRITE(vn, &u);
if (result) {
return result;
}
of->offset = u.uio_offset;
nwrite = size - u.uio_resid;
return (nwrite);
}

20
Q

load_elf function

A

Function where to look to understand how to implement read/write operations.

What is it doing?

Takes address space from the process, and then it executes the I/O operation.

In the first loop, we iterate program header number (e_phnum) of times (3):

The first section will not be read.

In the other two segments we’ll read the program header -> used to in order to define a region in the address space, this is done through as_define_region followd by as_prepare_load

In the second loop we iterate over the same thing, but this time we actually load the data into the previously defined regions, this is followed by as_complete_load.

int load_elf(struct vnode *v, vaddr_t *entrypoint){

Elf_Ehdr eh; /* Executable header */

Elf_Phdr ph; /* “Program header” = segment header */

int result, i;

struct iovec iov;

struct uio ku;

struct addrspace *as;

as = proc_getas();

/*

* Read the executable header from offset 0 in the file.

*/

uio_kinit(&iov, &ku, &eh, sizeof(eh), 0, UIO_READ);

result = VOP_READ(v, &ku);

if (result) {

return result;

}

if (ku.uio_resid != 0) {

/* short read; problem with executable? */

kprintf(“ELF: short read on header - file truncated?\n”);

return ENOEXEC;

}

/*

* Check to make sure it’s a 32-bit ELF-version-1 executable

* for our processor type. If it’s not, we can’t run it.

*

* Ignore EI_OSABI and EI_ABIVERSION - properly, we should

* define our own, but that would require tinkering with the

* linker to have it emit our magic numbers instead of the

* default ones. (If the linker even supports these fields,

* which were not in the original elf spec.)

*/

if (eh.e_ident[EI_MAG0] != ELFMAG0 ||

eh. e_ident[EI_MAG1] != ELFMAG1 ||
eh. e_ident[EI_MAG2] != ELFMAG2 ||
eh. e_ident[EI_MAG3] != ELFMAG3 ||
eh. e_ident[EI_CLASS] != ELFCLASS32 ||
eh. e_ident[EI_DATA] != ELFDATA2MSB ||
eh. e_ident[EI_VERSION] != EV_CURRENT ||
eh. e_version != EV_CURRENT ||
eh. e_type!=ET_EXEC ||
eh. e_machine!=EM_MACHINE) {

return ENOEXEC;

}

/*

* Go through the list of segments and set up the address space.

*

* Ordinarily there will be one code segment, one read-only

* data segment, and one data/bss segment, but there might

* conceivably be more. You don’t need to support such files

* if it’s unduly awkward to do so.

*

* Note that the expression eh.e_phoff + i*eh.e_phentsize is

* mandated by the ELF standard - we use sizeof(ph) to load,

* because that’s the structure we know, but the file on disk

* might have a larger structure, so we must use e_phentsize

* to find where the phdr starts.

*/

for (i=0; i

off_t offset = eh.e_phoff + i*eh.e_phentsize;

uio_kinit(&iov, &ku, &ph, sizeof(ph), offset, UIO_READ);

result = VOP_READ(v, &ku);

if (result) {

return result;

}

if (ku.uio_resid != 0) {

/* short read; problem with executable? */

kprintf(“ELF: short read on phdr - file truncated?\n”);

return ENOEXEC;

}

switch (ph.p_type) {

case PT_NULL: /* skip */ continue;

case PT_PHDR: /* skip */ continue;

case PT_MIPS_REGINFO: /* skip */ continue;

case PT_LOAD: break;

default:

kprintf(“loadelf: unknown segment type %d\n”,

ph.p_type);

return ENOEXEC;

}

result = as_define_region(as,

ph. p_vaddr, ph.p_memsz,
ph. p_flags & PF_R,
ph. p_flags & PF_W,
ph. p_flags & PF_X);

if (result) {

return result;

}

}

result = as_prepare_load(as);

if (result) {

return result;

}

/*

* Now actually load each segment.

*/

for (i=0; i

off_t offset = eh.e_phoff + i*eh.e_phentsize;

uio_kinit(&iov, &ku, &ph, sizeof(ph), offset, UIO_READ);

result = VOP_READ(v, &ku);

if (result) {

return result;

}

if (ku.uio_resid != 0) {

/* short read; problem with executable? */

kprintf(“ELF: short read on phdr - file truncated?\n”);

return ENOEXEC;

}

switch (ph.p_type) {

case PT_NULL: /* skip */ continue;

case PT_PHDR: /* skip */ continue;

case PT_MIPS_REGINFO: /* skip */ continue;

case PT_LOAD: break;

default:

kprintf(“loadelf: unknown segment type %d\n”,

ph.p_type);

return ENOEXEC;

}

result = load_segment(as, v, ph.p_offset, ph.p_vaddr,

ph. p_memsz, ph.p_filesz,
ph. p_flags & PF_X);

if (result) {

return result;

}

}

result = as_complete_load(as);

if (result) {

return result;

}

*entrypoint = eh.e_entry;

return 0;

}

21
Q

VOP_READ: what to do before using it

A

uio_kinit(&iov, &ku, &eh, sizeof(eh), 0, UIO_READ);

result = VOP_READ(v, &ku);

VOP_READ can be used either on emufs or sfs (simple file system)

22
Q

struct uio

A

uio struct points to an iov(ector)

iov: io vector is essentially a vector of memory destinations for the io, in the simplest form is an array of just one destination.

iov is a struct of:

  • iov_base
  • iov_len (size)

uio_iovcnt: iov vector len

user space:

  • we are doing stuff in user space when uio_segflg = UIO_USERSPACE
  • uio_space is not null, uio_space must point to an address space
    • because we need an address space to convert logical addresses to physical

kernel space:

  • uio_space can be null
    • because we to convert it to the physical address we just need to subtract kseg0 (2GB)
23
Q

lock_acquire, lock_release

A

if OPT_SYNCHSEM

lock_acquire(struct lock *lock)

{

KASSERT(lock != NULL);

KASSERT(curthread != NULL);

// decrement count of semaphore

P(lock->lk_sem);

spinlock_acquire(&lock->lk_slock);

lock->lk_owner = curthread;

spinlock_release(&lock->lk_slock);

spinlock_acquire(&lock->lk_slock);

while (lock->lk_held)

wchan_sleep(lock->lk_wchan, &lock->lk_slock);

KASSERT(lock->lk_held == false);

// The lock either has been released from another thread

// or was not held.

lock->lk_held = true;

lock->lk_owner = curthread;

spinlock_release(&lock->lk_slock);

}

void

lock_release(struct lock *lock)

{

KASSERT(lock != NULL);

KASSERT(curthread != NULL);

// increment count of semaphore

spinlock_acquire(&lock->lk_slock);

KASSERT(curthread == lock->lk_owner);

lock->lk_owner = NULL;

spinlock_release(&lock->lk_slock);

V(lock->lk_sem);

/*

* 1. Reset lock status (needs slock for ownership and value)

* 2. Wake one of the waiting threads (needs wchan_slock)

*/

spinlock_acquire(&lock->lk_slock);

KASSERT(curthread == lock->lk_owner);

KASSERT(lock->lk_held);

lock->lk_owner = NULL;

lock->lk_held = false;

wchan_wakeone(lock->lk_wchan, &lock->lk_slock);

spinlock_release(&lock->lk_slock);

}

24
Q

new program instance

A

In os161 the thread library is kernel level.

L’esecuzione di un programma utente in OS161 passa attraverso le seguenti fasi:

  • generazione di un thread (che eseguirà il programma) con creazione di un adeguato spazio di indirizzamento utente
  • Chiamata della funzione loadElf, con il compito di caricare il file eseguibile nello spazio di indirizzamentocreato. La lettura passa attraverso una fase di lettura dell’header, con dimensionamento dei relativi segmenti (codice e dati)
  • Una volta caricato il file Elf, ne viene attivata l’esecuzione mediante switch alla modalità user (fatta inassembler)
  • L’uscita dal thread va gestita opportunamente (mediante la system call exit

cmd_prog ->
common_prog(nargs, args) ->
proc_create_runprogram(progname) ->
thread_fork(progname, proc, cmd_progthread, args, nargs) ->
copy prog name
runprogram(progname) ->
vfs_open(progname, O_RDONLY, 0, &v)
as = as_create()
proc_set_as(as)
as_activate
load_elf(v, &entrypoint); // entrypoint is the 0000 of the proc
vfs_close(v);
as_define_stack(as, &stackptr)
enter_new_process(argc, userptr_t argv, userptr_t env, vaddr_t stackptr, vaddr_t, entrypoint) ->
mips_usermode -> asm_usermode
sys_waitpid

Enter new process: is a function that transforms the thread into a user process that in order to start behaves like returning from a trap.

As trap is a trap into the kernel, when the trap handling ends the user process is going to resume from the point where it stopped, in this case: the beginning.

Starting a new process is sort of putting it into a state that is like returning from a trap.

Enter new process is preparing a trap frame.

asm_usermode starts the process by jumping to exception_return.

25
Q

thread switchframe

A

data structure used to save the context of a thread, while not in execution.

The data segment of that cointains the thread library has a pointer to the switchframe that is stored in the stack.

26
Q

Thread life cycle functions: from ready to exec

A
27
Q

from thread fork to thread exec

A
28
Q

as define region

A

Using the logical address and the size of a given segment in order to allocate physical memory and in order to put the physical memory in correspondance with the logical memory.

actually as_define_region doesn’t alloca, it as_prepare_load that allocates the necessary memory, as_define_region

29
Q

load_segment

A

First part of load_elf acquires from the elf file, in a local variable in kernel memory, the header of the elf file: it is therefore an I/O of type UIO_SYSSPACE. The load_segment instead, must acquire the actual segments from the elf file to the user memory partitions just allocated for the process: the I/O is therefore of the UIO_USERISPACE type for the code (instructions) and UIO_USERSPACE for the data.

int

load_segment(struct addrspace *as, struct vnode *v,

off_t offset, vaddr_t vaddr,

size_t memsize, size_t filesize,

int is_executable)

{

struct iovec iov;

struct uio u;

int result;

if (filesize > memsize) {

kprintf(“ELF: warning: segment filesize > segment memsize\n”);

filesize = memsize;

}

DEBUG(DB_EXEC, “ELF: Loading %lu bytes to 0x%lx\n”,

(unsigned long) filesize, (unsigned long) vaddr);

iov. iov_ubase = (userptr_t)vaddr;
iov. iov_len = memsize; // length of the memory space
u. uio_iov = &iov;
u. uio_iovcnt = 1;
u. uio_resid = filesize; // amount to read from the file
u. uio_offset = offset;
u. uio_segflg = is_executable ? UIO_USERISPACE : UIO_USERSPACE;
u. uio_rw = UIO_READ;
u. uio_space = as;

result = VOP_READ(v, &u);

if (result) {

return result;

}

if (u.uio_resid != 0) {

/* short read; problem with executable? */

kprintf(“ELF: short read on segment - file truncated?\n”);

return ENOEXEC;

}

}

30
Q

sys_read

A

static int
file_read(int fd, userptr_t buf_ptr, size_t size) {
struct iovec iov;
struct uio u;
int result;
struct vnode *vn;
struct openfile *of;

if (fd<0||fd>OPEN_MAX) return -1;
of = curproc->fileTable[fd];
if (of==NULL) return -1;
vn = of->vn;
if (vn==NULL) return -1;

iov. iov_ubase = buf_ptr;
iov. iov_len = size;

u. uio_iov = &iov;
u. uio_iovcnt = 1;
u. uio_resid = size; // amount to read from the file
u. uio_offset = of->offset;
u. uio_segflg =UIO_USERISPACE;
u. uio_rw = UIO_READ;
u. uio_space = curproc->p_addrspace;

result = VOP_READ(vn, &u);
if (result) {
return result;
}

of->offset = u.uio_offset;
return (size - u.uio_resid);
}