man(1) pages have been added to the devwsys-prev repository. Html versions are available in the wiki of the project at bitbucket. The new manual pages are:
domingo, 21 de agosto de 2011
Wctl: a killer service
In Plan 9, rio(1) does not only manage windows. In fact, it is also a process manager. We create a new window to make a new process. When we want to finish it, we delete that window. Devwsys, on the other hand, is running on a host system, and therefore it cannot create or delete processes.
What we need to create processes is a service in the hosted Plan 9 system which mounts devwsys and runs the given command. This is what the wctl script does, announcing a file descriptor in /srv and reading new commands, in the format of rio's wctl service.
A more interesting problem is how to finish a process when that window is deleted. Rio reads a pid as part of the attach specifier. When the user asks to delete a window (with the mouse or a wctl message) a different thread sends a note to the process, waits a bit, kill it and, only then, deletes the window.
Devwsys cannot create or finish processes, so it needs a method to say it to wctl. The IPC mechanism used is, of course, 9P. The pid in the attach specifier is read by devwsys and stored. When the user wants to close a window (with a wctl message or a request from the X window manager), a read of the file /kill will return that pid. If we read the file before the window has been asked for deletion it will just block. That way, we can wait for a window to be deleted. If there is nobody blocked reading /kill, the window is immediately deleted.
The "killer" process has access to the kill file mounting the root of the devwsys file system, using the attach specifier "/". If this specifier is given to mount, a new window will not be created. Instead, we will have access to a file tree containing the kill file and the wsys and draw directories (where the draw directory does not contain a new file, because there is no associated window).
The first thing the wctl script does is mounting the root fs. Each time a pid is read from the kill file, killproc will send a kill note to the process, wait 0.2 seconds, and then will send kill to the ctl file of the process. Once the process finishes all the references to its window file system are lost, and the window is destroyed by devwsys (in theory, acme for example is not so simple).
What we need to create processes is a service in the hosted Plan 9 system which mounts devwsys and runs the given command. This is what the wctl script does, announcing a file descriptor in /srv and reading new commands, in the format of rio's wctl service.
A more interesting problem is how to finish a process when that window is deleted. Rio reads a pid as part of the attach specifier. When the user asks to delete a window (with the mouse or a wctl message) a different thread sends a note to the process, waits a bit, kill it and, only then, deletes the window.
Devwsys cannot create or finish processes, so it needs a method to say it to wctl. The IPC mechanism used is, of course, 9P. The pid in the attach specifier is read by devwsys and stored. When the user wants to close a window (with a wctl message or a request from the X window manager), a read of the file /kill will return that pid. If we read the file before the window has been asked for deletion it will just block. That way, we can wait for a window to be deleted. If there is nobody blocked reading /kill, the window is immediately deleted.
The "killer" process has access to the kill file mounting the root of the devwsys file system, using the attach specifier "/". If this specifier is given to mount, a new window will not be created. Instead, we will have access to a file tree containing the kill file and the wsys and draw directories (where the draw directory does not contain a new file, because there is no associated window).
The first thing the wctl script does is mounting the root fs. Each time a pid is read from the kill file, killproc will send a kill note to the process, wait 0.2 seconds, and then will send kill to the ctl file of the process. Once the process finishes all the references to its window file system are lost, and the window is destroyed by devwsys (in theory, acme for example is not so simple).
martes, 16 de agosto de 2011
Libninep
When this project was started one of the first questions that had to be solved was how to serve the 9P protocol from unix. We already depend on some Inferno libraries and Inferno includes a library to write 9P servers, libstyx. This library looks like an obvious choice. However, libstyx has some limitations: it does not support out-of-order replies, does not give the option to select on extra fds (we need that for the connection with X) and its representation of file trees is somewhat static.
Libninep is a modified libstyx, which tries to solve these issues.
Most of the content of styx(10) and styxserver(10) still applies to libninep (after s/styx/ninep/g, of course). The most important differences are explained in the next paragraphs.
Main loop
The loop of a libstyx application has this form:
While styxwait just waits for a fd to be ready and let styxprocess to do all the work, ninepwait will read the next 9P request when possible, setting s->curc and s->fcall. ninepdefault will process this request, using s->ops when available, and probably turning a Tmessage into a Rmessage. Only when ninepreply is called this request is actually replied. This way, we can use ninepdefault for common operations but, if that is not enough, we can also process 9P requests on our own, just filling the fields of s->fcall and calling ninepreply when we are done.
What devwsys is doing is calling updaterefs after ninepdefault. This function will take care of updating the number of references to a window after an attach, a walk, or a clunk transaction. To do this with libstyx, we would have to give up on using its facilities to handle file trees and implement walks and dirreads ourselves.
Out of order replies
When reading event files (cons, mouse) the file server cannot reply immediately to a request, it has to wait until an event is received. This functionality is provided by two functions: ninepreplylater will put a request on hold and returns a pointer to a Pending struct, which can be passed to ninepcompleted to reply when we are done. After calling ninepreplylater, ninepreply will have no effect until ninepwait is called again.
It is the responsibility of the client to keep a queue of pending requests if desired. However, libninep will take care of marking them as flushed if a Tflush request is made.
File trees: binding
Libstyx includes some functions to help with the creation of file trees: styxadddir and styxaddfile. The advantage of using these functions is that using them we do not have to worry about implementing walks or directory reads, but they do not allow us to give several entry points to a given file. For example, devwsys has to give access to the files of a window in the wsys directory of every other window and on its root, and the clients of draw should be accessible by every window.
This problem is solved in Linux using links. The same problem is solved by Plan 9 (and Inferno) in a more elegant way by bind. So, libninep includes a ninepbind function. Its usage is quite simple: given the qid of two directories (pqid and qid) the files of qid will also be accessible from pqid.
In devwsys, the file system root only includes two directories: /wsys and /draw:
Libninep is a modified libstyx, which tries to solve these issues.
Most of the content of styx(10) and styxserver(10) still applies to libninep (after s/styx/ninep/g, of course). The most important differences are explained in the next paragraphs.
Main loop
The loop of a libstyx application has this form:
After styxwait returns, styxproccess reads the next request, looks for the corresponding function in s->ops, runs it if found, and replies. With libninep we have more options. The main loop of devwsys looks like this:for(;;) styxwait(&s); styxproccess(&s); }
There are some new library functions here: nineplisten adds a fd to the fd set to select from and ninepready checks if it is ready. The styxprocess function has been split in two: ninepdefault and ninepreply. However, the key difference is ninepwait, which is doing more than styxwait.for(;;) nineplisten(&s, xfd); ninepwait(&s); if(ninepready(&s, xfd) xevent(); ninepdefault(&s); updaterefs(); ninepreply(&s); }
While styxwait just waits for a fd to be ready and let styxprocess to do all the work, ninepwait will read the next 9P request when possible, setting s->curc and s->fcall. ninepdefault will process this request, using s->ops when available, and probably turning a Tmessage into a Rmessage. Only when ninepreply is called this request is actually replied. This way, we can use ninepdefault for common operations but, if that is not enough, we can also process 9P requests on our own, just filling the fields of s->fcall and calling ninepreply when we are done.
What devwsys is doing is calling updaterefs after ninepdefault. This function will take care of updating the number of references to a window after an attach, a walk, or a clunk transaction. To do this with libstyx, we would have to give up on using its facilities to handle file trees and implement walks and dirreads ourselves.
Out of order replies
When reading event files (cons, mouse) the file server cannot reply immediately to a request, it has to wait until an event is received. This functionality is provided by two functions: ninepreplylater will put a request on hold and returns a pointer to a Pending struct, which can be passed to ninepcompleted to reply when we are done. After calling ninepreplylater, ninepreply will have no effect until ninepwait is called again.
It is the responsibility of the client to keep a queue of pending requests if desired. However, libninep will take care of marking them as flushed if a Tflush request is made.
File trees: binding
Libstyx includes some functions to help with the creation of file trees: styxadddir and styxaddfile. The advantage of using these functions is that using them we do not have to worry about implementing walks or directory reads, but they do not allow us to give several entry points to a given file. For example, devwsys has to give access to the files of a window in the wsys directory of every other window and on its root, and the clients of draw should be accessible by every window.
This problem is solved in Linux using links. The same problem is solved by Plan 9 (and Inferno) in a more elegant way by bind. So, libninep includes a ninepbind function. Its usage is quite simple: given the qid of two directories (pqid and qid) the files of qid will also be accessible from pqid.
In devwsys, the file system root only includes two directories: /wsys and /draw:
When a new window is created (due to an attach request), files for that window are created inside the corresponding /wsys/n/ directory:ninepadddir(s, Qroot, Qwsys, "wsys", 0555, eve); ninepadddir(s, Qroot, Qdraw, "draw", 0555, eve);
Then, we bind the directories /wsys and /draw to the corresponding directories of the window:p = PATH(w->id, 0); ninepadddir(s, Qwsys, p, name, 0555, eve); ninepaddfile(s, p, p|Qcons, "cons", 0666, eve); ninepaddfile(s, p, p|Qconsctl, "consctl", 0222, eve); ... ninepadddir(s, p, p|Qwsys, "wsys", 0555, eve); ninepadddir(s, p, p|Qdraw, "draw", 0555, eve); ninepaddfile(s, p|Qdraw, p|Qnew, "new", 0666, eve);
ninepbind(s, p|Qwsys, Qwsys); ninepbind(s, p|Qdraw, Qdraw);
When a new client attachs to devwsys, wsysattach (which is server->ops->attach) will be called. This functions sets the qid to be returned to be the one corresponding to /wsys/n and the binding magic will take care automatically of walking and reading directories.
Note 1: rio's fs lets you to walk to /wsys/n/wsys, but no further. Devwsys, which uses libninep and bind directories, does not have this limit. You can walk as deep as you want into /wsys/n1/wsys/n2/wsys/n1/wsys/...
Note 2: The files of every draw client are added to /draw/n. Although /draw is bound to /wsys/n/draw we still have access to the /wsys/n/draw/new file which corresponds to the window.Announce on unix addresses
You can only post your 9P server to a port using libstyx. Instead, libninep can also listen on unix!... addresses, as libixp does. A new function, ninepnamespace, gives the default namespace to use, maybe read from the environment variable NAMESPACE, else of the form /tmp/ns.$USER.$DISPLAY/ (as p9p and libixp do).
Suscribirse a:
Entradas (Atom)