| This file lists subtle things that might not be commented | |
| as well as they should be in the source code and that | |
| might be worth pointing out in a longer explanation or in class. | |
| --- | |
| [2009/07/12: No longer relevant; forkret1 changed | |
| and this is now cleaner.] | |
| forkret1 in trapasm.S is called with a tf argument. | |
| In order to use it, forkret1 copies the tf pointer into | |
| %esp and then jumps to trapret, which pops the | |
| register state out of the trap frame. If an interrupt | |
| came in between the mov tf, %esp and the iret that | |
| goes back out to user space, the interrupt stack frame | |
| would end up scribbling over the tf and whatever memory | |
| lay under it. | |
| Why is this safe? Because forkret1 is only called | |
| the first time a process returns to user space, and | |
| at that point, cp->tf is set to point to a trap frame | |
| constructed at the top of cp's kernel stack. So tf | |
| *is* a valid %esp that can hold interrupt state. | |
| If other tf's were used in forkret1, we could add | |
| a cli before the mov tf, %esp. | |
| --- | |
| In pushcli, must cli() no matter what. It is not safe to do | |
| if(cpus[cpu()].ncli == 0) | |
| cli(); | |
| cpus[cpu()].ncli++; | |
| because if interrupts are off then we might call cpu(), get | |
| rescheduled to a different cpu, look at cpus[oldcpu].ncli, | |
| and wrongly decide not to disable interrupts on the new cpu. | |
| Instead do | |
| cli(); | |
| cpus[cpu()].ncli++; | |
| always. | |
| --- | |
| There is a (harmless) race in pushcli, which does | |
| eflags = readeflags(); | |
| cli(); | |
| if(c->ncli++ == 0) | |
| c->intena = eflags & FL_IF; | |
| Consider a bottom-level pushcli. | |
| If interrupts are disabled already, then the right thing | |
| happens: read_eflags finds that FL_IF is not set, | |
| and intena = 0. If interrupts are enabled, then | |
| it is less clear that the right thing happens: | |
| the readeflags can execute, then the process | |
| can get preempted and rescheduled on another cpu, | |
| and then once it starts running, perhaps with | |
| interrupts disabled (can happen since the scheduler | |
| only enables interrupts once per scheduling loop, | |
| not every time it schedules a process), it will | |
| incorrectly record that interrupts *were* enabled. | |
| This doesn't matter, because if it was safe to be | |
| running with interrupts enabled before the context | |
| switch, it is still safe (and arguably more correct) | |
| to run with them enabled after the context switch too. | |
| In fact it would be safe if scheduler always set | |
| c->intena = 1; | |
| before calling swtch, and perhaps it should. | |
| --- | |
| The x86's processor-ordering memory model | |
| matches spin locks well, so no explicit memory | |
| synchronization instructions are required in | |
| acquire and release. | |
| Consider two sequences of code on different CPUs: | |
| CPU0 | |
| A; | |
| release(lk); | |
| and | |
| CPU1 | |
| acquire(lk); | |
| B; | |
| We want to make sure that: | |
| - all reads in B see the effects of writes in A. | |
| - all reads in A do *not* see the effects of writes in B. | |
| The x86 guarantees that writes in A will go out | |
| to memory before the write of lk->locked = 0 in | |
| release(lk). It further guarantees that CPU1 | |
| will observe CPU0's write of lk->locked = 0 only | |
| after observing the earlier writes by CPU0. | |
| So any reads in B are guaranteed to observe the | |
| effects of writes in A. | |
| According to the Intel manual behavior spec, the | |
| second condition requires a serialization instruction | |
| in release, to avoid reads in A happening after giving | |
| up lk. No Intel SMP processor in existence actually | |
| moves reads down after writes, but the language in | |
| the spec allows it. There is no telling whether future | |
| processors will need it. | |
| --- | |
| The code in fork needs to read np->pid before | |
| setting np->state to RUNNABLE. The following | |
| is not a correct way to do this: | |
| int | |
| fork(void) | |
| { | |
| ... | |
| np->state = RUNNABLE; | |
| return np->pid; // oops | |
| } | |
| After setting np->state to RUNNABLE, some other CPU | |
| might run the process, it might exit, and then it might | |
| get reused for a different process (with a new pid), all | |
| before the return statement. So it's not safe to just | |
| "return np->pid". Even saving a copy of np->pid before | |
| setting np->state isn't safe, since the compiler is | |
| allowed to re-order statements. | |
| The real code saves a copy of np->pid, then acquires a lock | |
| around the write to np->state. The acquire() prevents the | |
| compiler from re-ordering. | |
Xet Storage Details
- Size:
- 4.17 kB
- Xet hash:
- 324ddd0bad149d2b6a589a2e64f7f438818652dd2878cf4a64d133075f0c5fb0
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.