Writing just the kernel component, the device driver, isn't quite enough; you also have to write a user space application that will actually make use of the driver. We will do so here. (Again, you could simply use dd(1) as well.)
In order to use the device driver, the user space app must first, of course, open the device file corresponding to it. (Here, to save space, we don't show the app code in its entirety, just the most relevant portions of it. We expect you to have cloned the book's Git repository and to work on the code.) The code to open the device file is as follows:
// ch1/miscdrv_rdwr/rdwr_test_secret.c
int main(int argc, char **argv)
{
char opt = 'r';
int fd, flags = O_RDONLY;
ssize_t n;
char *buf = NULL;
size_t num = 0;
[...]
if ('w' == opt)
flags = O_WRONLY;
fd = open(argv[2], flags, 0);
if (fd == -1) {
[...]
The second argument to this app is the device file to open. In order to read or write, the process will require memory:
if ('w' == opt)
num = strlen(argv[3])+1; // IMP! +1 to include the NULL byte!
else
num = MAXBYTES;
buf = malloc(num);
if (!buf) {
[...]
Moving along, let's see the block of code to have the app invoke a read or write (depending on the first parameter being r or w) on the (pseudo)device (for conciseness, we don't show the error handling code):
if ('r' == opt) {
n = read(fd, buf, num);
if( n < 0 ) [...]
printf("%s: read %zd bytes from %s\n", argv[0], n, argv[2]);
printf("The 'secret' is:\n \"%.*s\"\n", (int)n, buf);
} else {
strncpy(buf, argv[3], num);
n = write(fd, buf, num);
if( n < 0 ) [ ... ]
printf("%s: wrote %zd bytes to %s\n", argv[0], n, argv[2]);
}
[...]
free(buf);
close(fd);
exit(EXIT_SUCCESS);
}
(Before you try out this driver, do ensure the previous miscdrv driver's kernel module is unloaded.) Now, ensure that this driver is built and inserted, of course, else it will result in the open(2) system call failing. We have shown a couple of trial runs. First, let's build the user-mode app, insert the driver (not shown in Figure 1.8), and read from our just-created device node:
The user-mode app successfully receives 7 bytes from the driver; it's the (initial) secret value, which it displays. The kernel log reflects the driver initialization, and a few seconds later, you can see (via the dev_xxx() instances of printk we emitted) that the rdwr_test_secret app runs the drivers' code in process context. The opening of the device, the running of the subsequent read, and the close methods are clearly seen. (Notice how the process name is truncated to rdwr_test_secre; this is as the task structure's comm member is the process name truncated to 16 characters.)
In Figure 1.9, we show the complementary act of writing to our device node, changing the secret value; a subsequent read indeed reveals that it has worked:
The portion of the kernel log where the write takes place is highlighted in Figure 1.9. It works; I definitely encourage you to try this out yourself, looking up the kernel log as you go along.
Now, it's time to dig a little deeper. The reality is that as a driver author, you have to learn to be really careful regarding security, else all kinds of nasty surprises lie in wait. The next section gives you an understanding of this key area.