int fork(void)
{
int i, pid;
struct proc *np;
struct proc *curproc = myproc();
// Allocate process.
if((np = allocproc()) == 0){
return -1;
}
// Copy process state from proc.
if((np->pgdir = copyuvm(curproc->pgdir, curproc->sz)) == 0){
kfree(np->kstack);
np->kstack = 0;
np->state = UNUSED;
return -1;
}
np->sz = curproc->sz;
np->parent = curproc;
*np->tf = *curproc->tf;
// Clear %eax so that fork returns 0 in the child.
np->tf->eax = 0;
for(i = 0; i < NOFILE; i++)
if(curproc->ofile[i])
np->ofile[i] = filedup(curproc->ofile[i]);
np->cwd = idup(curproc->cwd);
safestrcpy(np->name, curproc->name, sizeof(curproc->name));
pid = np->pid;
acquire(&ptable.lock);
np->state = RUNNABLE;
release(&ptable.lock);
return pid;
}
O Fork faz uma cópia de um processo, mantendo o mesmo estado começa procurando na tabela de processos um lugar que esteja vazio através da função allocproc, se não encontrar nenhum retorna 0, a partir do momento que um espaço para a criação do processo foi encontrada, começa a cópia do estado do pai para o filho, primeiro checa-se se foi possível copiar a informação do diretório de páginas, retornando um erro se não foi possível, e então todas as informações que estão disponíveis no processo pai são copiadas, por fim o estado do filho é atualizado para RUNNABLE, e o pid dele é retornado para o pai.
O exec tem como função carregar um novo programa em um processo, para isso ele abre o diretório do programa e faz os ajustes necessários na tabela de processos para que tudo funcione, caso algo de errado ele libera a memória utilizada e retorna -1for.
void sched(void)
{
int intena;
struct proc *p = myproc();
if(!holding(&ptable.lock))
panic("sched ptable.lock");
if(mycpu()->ncli != 1)
panic("sched locks");
if(p->state == RUNNING)
panic("sched running");
if(readeflags()&FL_IF)
panic("sched interruptible");
intena = mycpu()->intena;
swtch(&p->context, mycpu()->scheduler);
mycpu()->intena = intena;
}
O código sched é o que irá chamar o scheduler, e tem a função de verificar se não ocorreu nenhum erro, e salva informações da cpu que não são compartilhadas no kernel.
void scheduler(void)
{
struct proc *p;
struct cpu *c = mycpu();
c->proc = 0;
for(;;){
sti();
acquire(&ptable.lock);
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
if(p->state != RUNNABLE)
continue;
c->proc = p;
switchuvm(p);
p->state = RUNNING;
swtch(&(c->scheduler), p->context);
switchkvm();
c->proc = 0;
}
release(&ptable.lock);
}
}
O scheduler habilita a interrupção na CPU, em seguida olha na tabela de processos, para procurar algum processo que esteja apto para executar, isto é, no estado RUNNABLE, quando ele encontra algum processo que esteja apto, ele realiza a troca de contexto, e o novo processo passa a executar na CPU.
void exit(void)
{
struct proc *curproc = myproc();
struct proc *p;
int fd;
if(curproc == initproc)
panic("init exiting");
// Close all open files.
for(fd = 0; fd < NOFILE; fd++){
if(curproc->ofile[fd]){
fileclose(curproc->ofile[fd]);
curproc->ofile[fd] = 0;
}
}
begin_op();
iput(curproc->cwd);
end_op();
curproc->cwd = 0;
acquire(&ptable.lock);
// Parent might be sleeping in wait().
wakeup1(curproc->parent);
// Pass abandoned children to init.
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
if(p->parent == curproc){
p->parent = initproc;
if(p->state == ZOMBIE)
wakeup1(initproc);
}
}
// Jump into the scheduler, never to return.
curproc->state = ZOMBIE;
sched();
panic("zombie exit");
}
O exit começa fechando todos os arquivos abertos pelo processo, em seguida limpa da tabela de processos o diretório do processo que se está terminando, tenta acordar o processo pai, e passa todos os filhos que podem ter sido abandonados para o processo init, que irá tratar eles de algum modo, por fim declara que o processo virou ZOMBIE, e chama o escalonador de processos.
int kill(int pid) { struct proc *p; acquire(&ptable.lock); for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){ if(p->pid == pid){ p->killed = 1; // Wake process from sleep if necessary. if(p->state == SLEEPING) p->state = RUNNABLE; release(&ptable.lock); return 0; } } release(&ptable.lock); return -1; }
Trecho de código do arquivo trap.c,que faz o programa terminar.
if(myproc() && myproc()->killed && (tf->cs&3) == DPL_USER) exit(); // Force process to give up CPU on clock tick. // If interrupts were on while locks held, //would need to check nlock. if(myproc() && myproc()->state == RUNNING && tf->trapno == T_IRQ0+IRQ_TIMER) yield(); // Check if the process has been killed since we yielded if(myproc() && myproc()->killed && (tf->cs&3) == DPL_USER) exit();
O kill procura a pid do processo que se deseja matar, e se encontrar o marca como morto Como o processo está executando em espaço de kernel ele ainda não é apagado, só vem a ser realmente apagado quando retorna para o espaço do usuário, e lá é capturado por uma trap e é forçado a chamar a função exit.
int wait(void)
{
struct proc *p;
int havekids, pid;
struct proc *curproc = myproc();
acquire(&ptable.lock);
for(;;){
// Scan through table looking for exited children.
havekids = 0;
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
if(p->parent != curproc)
continue;
havekids = 1;
if(p->state == ZOMBIE){
// Found one.
pid = p->pid;
kfree(p->kstack);
p->kstack = 0;
freevm(p->pgdir);
p->pid = 0;
p->parent = 0;
p->name[0] = 0;
p->killed = 0;
p->state = UNUSED;
release(&ptable.lock);
return pid;
}
}
static void wakeup1(void *chan)
{
struct proc *p;
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++)
if(p->state == SLEEPING && p->chan == chan)
p->state = RUNNABLE;
}
void yield(void)
{
acquire(&ptable.lock); //DOC: yieldlock
myproc()->state = RUNNABLE;
sched();
release(&ptable.lock);
}
void sleep(void *chan, struct spinlock *lk)
{
struct proc *p = myproc();
if(p == 0)
panic("sleep");
if(lk == 0)
panic("sleep without lk");
if(lk != &ptable.lock){
acquire(&ptable.lock);
release(lk);
}
// Go to sleep.
p->chan = chan;
p->state = SLEEPING;
sched();
p->chan = 0;
if(lk != &ptable.lock){
release(&ptable.lock);
acquire(lk);
}
}
A função wait faz que o processo espere o retorno de algum filho dele. O código começa verificando se existe algum filho do processo, caso contrário retornar um erro, se encontrou algum filho, verifica se ele já terminou de processar vendo se ele é um processo ZOMBIE, então faz todas as limpezas na memória, que sejam necessárias, e retorna a pid do filho. Se nenhum filho terminou a sua execução, o processo pai fica bloqueado por um sleep, até que algum filho termine de processar mandando assim um wakeup para o pai.
A função sleep faz com que o processo seja bloqueado e só possa ser executado novamente quando o evento que o fez ser bloqueado termine. O código começa travando o lock da tabela de processos, para poder mudar os atributos do processo, e então chama o escalonador.
A função wakeup serve para indicar para o processo que o evento que ele esperava terminou, e ele possa ser executado novamente. O código procura por processos na tabela de processo, que estejam dormindo e tenham o chan(canal) desejado para serem acordados, e caso encontre, o estado dos processos é mudado para RUNNABLE.
A função YIELD faz com que o processo abra mão do seu tempo de processamento, durante um ciclo de escalonamento. A função define o estado do processo com RUNNABLE e chama o escalonador.