| We're adding the plan 9 file system to akaros.We're bringing in the |
| name space interface, including the ability to do binds, union mounts, |
| and the like. We will extend it to support things we might need, |
| in particular mmap. We will use it to add things to Akaros we |
| need, such as virtio drivers, ethernet drivers, and TCP. |
| |
| By bringing this model in, we can make the Akaros interface more powerful, |
| yet simpler. We can remove a number of system calls right away |
| and yet still have the functions they provide, for example. |
| |
| This is not a from scratch effort but a code translation. The Plan 9 code |
| deals with very subtle situations and has been hardened over time. No need |
| to relearn what they learned from scratch. |
| |
| Currently we have a lot of the code in and are testing a first device -- |
| the regression device from NxM. |
| |
| Issues. |
| |
| The biggest issue so far is implementing the Plan 9 error handling. |
| In Plan 9, errors are managed via a longjmp-like mechanism. For example, |
| in a call chain such as: |
| a() |
| b() |
| c() |
| d() |
| |
| It is possible for 'd' to invoke an error that returns to 'a' directly. |
| This model has many advantages over the standard model, which looks like |
| this: |
| a{ b();} |
| b{ if c() return OK; else return -1;} |
| c{ if d() return OK; else return -1;} |
| d{ if something return OK; else return -1;} |
| |
| In Plan 9 it can look like this: |
| |
| a{ b();} |
| b{ c(); something else();} |
| c{ d(); other thing();} |
| d{ if something return OK; else error("bad");} |
| |
| Note that the intermediate functions can be written as though nothing |
| went wrong, since anything that goes wrong gets bounced to the first level |
| error handler. |
| |
| The handling is implemented via something called waserror(). |
| In a() we do this: |
| |
| a(){ |
| if (waserror()) { handle error; poperror();} |
| b(); |
| poperror(); |
| } |
| |
| and in d we might have this: |
| d(){ |
| do something; |
| if (bad) error("bad"); |
| return 0; |
| } |
| |
| What if there's more than one error? There can be multiple invocations |
| of waserror in one function: |
| a(){ |
| if (waserror()){ printf("b failed"); poperror(); return -2;} |
| b(); |
| if (waserror()) { printf("z failed"); nexterror(); } |
| z(); |
| poperror(); |
| poperror(); |
| } |
| |
| Every waserror needs a matching poperror or nexterror in the same function as |
| the waserror, covering every exit from the function. nexterror is like |
| to poperror() then error("str"), but without resetting errstr. |
| |
| Note that the error could have been anywhere in the call chain; |
| we don't care. From the point of view of a(), something failed, and we only |
| know about b() or z(), so we blame them. We also show in this example |
| nexterror(). Nexterror() pops back to the next error in the error stack, |
| which might be in this function or somewhere up the call chain. |
| |
| How do we find out the ultimate blame? Recall that error takes a string, |
| and that can be anything. We can tell from that. |
| |
| Where does the string in error() go? |
| In Plan 9, it goes into the proc struct; in Akaros, |
| it's copied to a user-mode buffer via set_errstr(). |
| |
| waserror()/error()/nexterror() manipulate an error call stack, similar to |
| the function call stack. In Plan 9, this stack is maintained in the proc |
| struct. This is cheap in Plan 9, since the compiler has caller-save, and |
| hence each stack entry is a stack and a PC. In a callee-save world, the |
| stack entries are much, much larger; so large that maintaining the stack |
| in the proc struct is impractical. |
| |
| Hence, we've had to make changes that add a bit of inconvenience but |
| leave the code mostly intact. The error code in Akaros is tested in every |
| circumstance at this point due to all the bugs we had in our port |
| of the Plan 9 file system code. |
| |
| So, we'll go from the easiest case to the hardest. |
| |
| Case 1: You're a leaf function that does not use error(). No change. |
| |
| Case 2: You're a leaf function that needs error(). Just call error(). |
| |
| Case 3: You're an intermediate function that calls functions that use error(), |
| even though you do not. No change. |
| |
| Those are in some sense the easier cases. Now it starts to get a |
| bit harder. |
| |
| Case 4: you're a leaf or intermediate function that uses waserror(). You need |
| to use a macro which creates an array of errbuf structs as automatics. The |
| waserror() usage does not change. The macro is ERRSTACK(x), where x is the |
| number of calls to waserror() in your function. See kern/sys/chan.c. Every call |
| to waserror needs to have a matching poperror. You cannot call nexterror or |
| poperror unless you are in the same scope as an ERRSTACK that had a waserror |
| call. |
| |
| Case 5: you're a root node, i.e. you're the start of a chain of calls |
| via syscall that must do the "root" errbuf setup, so that all error |
| calls eventually return to you. In this case, you need to start the error |
| stack. This uses the same macro as case 4 (ERRSTACK(x)), for now. |
| |
| Finally, if, in a waserror, you are finished and want to pop out to the |
| next error in the chain, either in the same function or up the call stack, |
| just call nexterror(). |
| |
| This can be handy for debugging: in any function that supports error(), i.e. |
| called from a function that called waserror(), simply call error and it bails |
| you out of wherever you are, doing appropriate cleanup on the way out. No need |
| to add return value processing to intermediate functions; if you try this out |
| you will likely find it is extremely handy. I really miss it on Linux. It made a |
| lot of the port debugging to Akaros a lot easier. |
| |
| There is error checking in error()/waserror()/nexterror(). |
| If you overflow or underflow the error stack the kernel will panic. |
| The implementation is in kern/src/error.c, the macros are in |
| kern/include/plan9.h. |
| |
| Giant warning: if you access any automatic (local) variables inside the waserror |
| block that may have been modified after you started the waserror, those |
| variables need to be volatile. waserror() is implemented with setjmp and |
| according to one of the man pages: |
| |
| automatic variables are unspecified after a call to longjmp() if |
| they meet all the following criteria: |
| - they are local to the function that made the corresponding setjmp(3) |
| call; |
| - their values are changed between the calls to setjmp(3) and |
| longjmp(); and |
| - they are not declared as volatile. |
| |
| We could ask the compiler to tell us which variables are potentially clobbered |
| with -Wclobbered, however it is a noisy warning. It will warn even if the |
| variables are not used in the error case. That may be because the compiler has |
| a hard time deciding whether a variable is used or not, since we often longjmp |
| from within an error handler, though on gcc 4.9.2, even if we return |
| immediately, we still get a warning. On a related note, it's not always |
| possible to tell which case is the error handling case - consider the "discard" |
| pattern for waserror. |
| |
| No amount of "returns_twice" or register clobbering or "memory" clobbering is |
| enough. Think about what happens when the variable is changed after the setjmp |
| call, i.e. farther down in the function. It's may be in a register, then we |
| call some other function, and that longjmps. That register value is gone (it |
| might be somewhere else on the stack). The compiler needs to know when it makes |
| that write that it needs to go onto the stack storage of the automatic variable. |
| That's 'volatile.' |
| |
| The best we can hope for is the compiler to know what variables could be written |
| from one side of the setjmp and used on the other. Perhaps that will show up, |
| and then we can turn on -Wclobbered. Until then, we have to be vigilant, or use |
| different patterns for waserror. Note there are a bunch of bugs with |
| -Wclobbered detection, e.g. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=65041. |
| |
| For info on the format of Ms (which are part of the kernel interface): |
| http://9p.cat-v.org/documentation/rfc/ |