MOP2 programming intro - add pipes
This commit is contained in:
@ -223,7 +223,7 @@ quite a bit of "freestanding" libraries that you can find online ;(.
|
|||||||
|
|
||||||
Printf rant over...
|
Printf rant over...
|
||||||
|
|
||||||
=== Error codes in MOP2
|
==== Error codes in MOP2 - a small anecdote
|
||||||
|
|
||||||
You might've noticed is that `main()` looks a little different from standard C `main()`. There's
|
You might've noticed is that `main()` looks a little different from standard C `main()`. There's
|
||||||
no return/error code, because MOP2 simply does not implement such feature. This is because MOP2 doesn't follow the
|
no return/error code, because MOP2 simply does not implement such feature. This is because MOP2 doesn't follow the
|
||||||
@ -243,3 +243,169 @@ so for eg. in MOP2, we have `$fs` for working with the filesystem or `$pctl` for
|
|||||||
approach things the MOP2 way, it turns out error codes are kind of useless (or at least they wouldn't get much
|
approach things the MOP2 way, it turns out error codes are kind of useless (or at least they wouldn't get much
|
||||||
use), since we don't need to connect many programs together to get something done.
|
use), since we don't need to connect many programs together to get something done.
|
||||||
|
|
||||||
|
=== Printing under the hood - intro to pipes
|
||||||
|
|
||||||
|
Let's take a look into what calling `uprintf()` actually does to print the characters post formatting. The printf
|
||||||
|
library requires the user to define a `putchar_()` function, which is used to render a single character.
|
||||||
|
Personally, I think that this way of printing text is inefficient and it would be better to output and entire
|
||||||
|
buffer of memory, but oh well.
|
||||||
|
|
||||||
|
.putchar.c
|
||||||
|
[source,c]
|
||||||
|
----
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <system/system.h>
|
||||||
|
#include <printf/printf.h>
|
||||||
|
|
||||||
|
void putchar_(char c) {
|
||||||
|
ipc_pipewrite(-1, 0, (uint8_t *const)&c, 1);
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
To output a single character we write it into a pipe. -1 means that the pipe belongs to the calling process, 0
|
||||||
|
is an ID into a table of process' pipes - and 0 means percisely the output pipe. In UNIX, the standard pipes are
|
||||||
|
numbered as 0 = stdin, 1 = stdout and 2 = stderr. In MOP2 there's no stderr, everything the application outputs
|
||||||
|
goes into the out pipe (0), so we can just drop that entirely. We're left with stdin/in pipe and stdout/out pipe,
|
||||||
|
but I've decided to swap them around, because the out pipe is used more frequently and it made sense to get it
|
||||||
|
working first and only then worry about getting input.
|
||||||
|
|
||||||
|
== Pipes
|
||||||
|
|
||||||
|
MOP2 pipes are a lot like UNIX pipes - they're a bidirectional stream of data, but there's slight difference in
|
||||||
|
the interface. Let's take a look at what ulib defines:
|
||||||
|
|
||||||
|
.Definitions for ipc_pipeXXX() calls
|
||||||
|
[source,c]
|
||||||
|
----
|
||||||
|
int32_t ipc_piperead(PID_t pid, uint64_t pipenum, uint8_t *const buffer, size_t len);
|
||||||
|
int32_t ipc_pipewrite(PID_t pid, uint64_t pipenum, const uint8_t *buffer, size_t len);
|
||||||
|
int32_t ipc_pipemake(uint64_t pipenum);
|
||||||
|
int32_t ipc_pipedelete(uint64_t pipenum);
|
||||||
|
int32_t ipc_pipeconnect(PID_t pid1, uint64_t pipenum1, PID_t pid2, uint64_t pipenum2);
|
||||||
|
----
|
||||||
|
|
||||||
|
In UNIX you have 2 processes working with a single pipe, but in MOP2, a pipe is exposed to the outside world and
|
||||||
|
anyone can read and write to it, which explains why these calls require a PID to be provided (indicates the
|
||||||
|
owner of the pipe).
|
||||||
|
|
||||||
|
.Example of ipc_piperead() - reading your applications own input stream
|
||||||
|
[source,c]
|
||||||
|
----
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <ulib.h>
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
PID_t pid = proc_getpid();
|
||||||
|
|
||||||
|
#define INPUT_LINE_MAX 1024
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
char buffer[INPUT_LINE_MAX];
|
||||||
|
string_memset(buffer, 0, sizeof(buffer));
|
||||||
|
int32_t nrd = ipc_piperead(pid, 1, (uint8_t *const)buffer, sizeof(buffer) - 1);
|
||||||
|
if (nrd > 0) {
|
||||||
|
uprintf("Got something: %s\n", buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
`ipc_pipewrite()` is a little boring, so let's not go over it. Creating, deleting and connecting pipes is where
|
||||||
|
things get interesting.
|
||||||
|
|
||||||
|
A common issue, I've encountered, while programming in userspace for MOP2 is that I'd want to spawn some external
|
||||||
|
application and collect it's output, for eg. into an ulib `StringBuffer` or some other akin structure. The
|
||||||
|
obvious thing to do would be to (since everything is polling-based) spawn an application, poll it's state (not
|
||||||
|
PROC_DEAD) and while polling, read it's out pipe (0) and save it into a stringbuffer. The code to do this would
|
||||||
|
look something like this:
|
||||||
|
|
||||||
|
.Pipe lifetime problem illustration
|
||||||
|
[source,c]
|
||||||
|
----
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <ulib.h>
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
StringBuffer outsbuf;
|
||||||
|
stringbuffer_init(&outsbuf);
|
||||||
|
|
||||||
|
char *appargs = { "-saystring", "hello world" };
|
||||||
|
int32_t myapp = proc_spawn("base:/bin/myapp", appargs, ARRLEN(appargs));
|
||||||
|
|
||||||
|
proc_run(myapp);
|
||||||
|
|
||||||
|
// 4 == PROC_DEAD
|
||||||
|
while (proc_pollstate(myapp) != 4) {
|
||||||
|
int32_t r;
|
||||||
|
char buf[100];
|
||||||
|
string_memset(buf, 0, sizeof(buf));
|
||||||
|
|
||||||
|
r = ipc_piperead(myapp, 0, (uint8_t *const)buf, sizeof(buf) - 1);
|
||||||
|
if (r > 0) {
|
||||||
|
stringbuffer_appendcstr(&outsbuf, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// print entire output
|
||||||
|
uprintf("%.*s\n", (int)outsbuf.count, outsbuf.data);
|
||||||
|
|
||||||
|
stringbuffer_free(&outsbuf);
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
Can you spot the BIG BUG? What if the application dies before we manage to read data from the pipe, taking the pipe
|
||||||
|
down with itself? We're then stuck in this weird state of having incomplete data and the app being reported as
|
||||||
|
dead by proc_pollstate.
|
||||||
|
|
||||||
|
This can be easily solved by changing the lifetime of the pipe we're working with. The *parent* process shall
|
||||||
|
allocate a pipe, connect it to it's *child* process and make it so that a child is writing into a pipe managed by
|
||||||
|
it's parent.
|
||||||
|
|
||||||
|
.Pipe lifetime problem - the solution
|
||||||
|
[source,c]
|
||||||
|
----
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <ulib.h>
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
PID_t pid = proc_getpid();
|
||||||
|
|
||||||
|
StringBuffer outsbuf;
|
||||||
|
stringbuffer_init(&outsbuf);
|
||||||
|
|
||||||
|
char *appargs = { "-saystring", "hello world" };
|
||||||
|
int32_t myapp = proc_spawn("base:/bin/myapp", appargs, ARRLEN(appargs));
|
||||||
|
|
||||||
|
// take a free pipe slot. 0 and 1 are already taken by default
|
||||||
|
ipc_pipemake(10);
|
||||||
|
// connect pipes
|
||||||
|
// myapp's out (0) pipe --> pid's 10th pipe
|
||||||
|
ipc_pipeconnect(myapp, 0, pid, 10);
|
||||||
|
|
||||||
|
proc_run(myapp);
|
||||||
|
|
||||||
|
// 4 == PROC_DEAD
|
||||||
|
while (proc_pollstate(myapp) != 4) {
|
||||||
|
int32_t r;
|
||||||
|
char buf[100];
|
||||||
|
string_memset(buf, 0, sizeof(buf));
|
||||||
|
|
||||||
|
r = ipc_piperead(myapp, 0, (uint8_t *const)buf, sizeof(buf) - 1);
|
||||||
|
if (r > 0) {
|
||||||
|
stringbuffer_appendcstr(&outsbuf, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// print entire output
|
||||||
|
uprintf("%.*s\n", (int)outsbuf.count, outsbuf.data);
|
||||||
|
|
||||||
|
ipc_pipedelete(10);
|
||||||
|
|
||||||
|
stringbuffer_free(&outsbuf);
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
Now, since the parent is managing the pipe and it outlives the child, everything is safe.
|
||||||
|
|||||||
Reference in New Issue
Block a user