...making Linux just a little more fun! |
By Nick Weber |
Introduction
Installation
Running
Example 1: Networking
Example 2: System Calls
Example 3: Device Drivers
Example 4: SysV IPC
Code
References
The first step to get TUN/TAP is to install uml_utilities. This can be obtained from the UML website. To install the utilities untar the file, cd into the created directory and type 'make install'. This will install five programs into /usr/bin with uml_net being the one that we are interested in. uml_net will help do the setup so that the host and UML can communicate. The only drawback of this method is that uml_net is a setuid program and can be a possible security vulnerability. The setup that the uml_net program does can also be done on the host machine as the root user. This will be covered in a later addition.
For this example we will setup the host with an ip of 10.0.0.1 and the UML with 10.0.0.2. On the host machine assign the ip address to the eth0 interface with 'ifconfig eth0 10.0.0.1'. Now we boot the the UML machine with the following command './linux eth0=tuntap,,,10.0.0.1'. There are four paramaters that can be specified for eth0, but we are interested in the first and last one for now. The first one tells UML which transport to use and the last paramter specifies the ip of the host machine. A point of confusion for many is the last paramter. This is the ip of the host machine and not what you want the ip of the UML to be. After booting login and run 'ifconfig eth0 10.0.0.2' on the UML machine. Now you should be able to ping, ssh, ftp, etc to the host machine from UML and vice versa.
You will need to make changes to three files in the UML kernel directory. Starting from the UMLkernel directory they are include/asm/arch/unistd.h, arch/um/kernel/sys_call_table.c, arch/um/kernel/Makefile. The code for the system call will go in the arch/um/kernel directory. Using the code from code section as an example do the following:
Shared Memory
Shared memory allows you to map unused memory to be used by multiple processes.
There are four functions that are used to set up and use a shared memory
segment. They are: shmget(), shmat(), shmdt(), shmctl(). Since these functions
are implemented through system calls we can expect to find the underlying
system call of: sys_shmget(), sys_shmat(), sys_shmdt(), sys_shmctl() for
each of the functions respectively. Shared memory in UML is done the same
as you would for the host kernel. Check out shrmem1_sysV.c and shrmem2_sysV.c
for the source code of two programs using a segment of shared memory. Compile
each program with gcc, start shrmem2_sysV in the background then run shrmem1_sysV.
Message Passing
Another way to share data between programs is throught the use of the message
passing API. Like shared memory, the message passing API also has four functions
with underlying system calls. The user functions are msgget(), msgsnd(),
msgrcv(), and msgctl(), while the system calls are sys_msgget(), sys_msgsnd(),
sys_msgrcv(), and sys_msgctl. For an example of message passing compile
the two source files recvmsg_sysV.c and sendmsg_sysV.c. Start recvmsg_sysV
in the background then run sendmsg_sysV to see message passing in action.
#include <linux/kernel.h>
asmlinkage int sys_my_new_call(void) {
printk(KERN_ALERT "sys_my_new_call at your service\n");
return 0;
}
#include <sys/types.h>
#include <linux/unistd.h>
static inline _syscall0(int, my_new_call);
int main() {
int result;
result = my_new_call();
}
#define true 1
#define false 0
/* This will be the name we choose for our device. We will also
use this as a prefix on functions such as the entry points
appearing in the file_operations struct.
*/
#define DEV_NAME "pp"
static int Major;
/* These are prototypes for residents of the file_operations struct
*/
static ssize_t pp_read(struct file *, char *, size_t, loff_t *);
static ssize_t pp_write(struct file *, const char *, size_t, loff_t *);
static int pp_open(struct inode *, struct file *);
static int pp_close(struct inode *, struct file *);
/* This is the file_operations struct. The init_module function
will register this with the kernel so the kernel will know all
the entry points it contains.
*/
struct file_operations Fops = {
owner: THIS_MODULE,
read: pp_read,
write: pp_write,
open: pp_open,
release: pp_close,
};
/* The pp_probe function does nothing here, but reminds us that a
'real' driver may need to probe for hardware resources. These
resources might later be allocated in init_module.
*/
static int pp_probe(void){
return 0;
}
/* The pp_read function is a stub, but at least does a printk,
for tracing purposes, when it is called.
*/
static ssize_t pp_read(struct file *file, char *buff, size_t ctr, loff_t *woof) {
printk(KERN_ALERT "\npp_read active.\n");
return 0;
}
/* The pp_write function is a stub, but at least does a printk,
for tracing purposes, when it is called.
*/
static ssize_t pp_write(struct file *file, const char *buff, size_t ctr, loff_t *woof) {
printk(KERN_ALERT "\npp_write active.\n");
return 0;
}
/* The pp_open function does a printk for tracing purposes.
*/
static int pp_open(struct inode *inode, struct file *file) {
printk(KERN_ALERT "\nAn instance of %s has been opened.\n", DEV_NAME);
return 0;
}
/* The pp_close function does a printk for tracing purposes.
*/
static int pp_close(struct inode *inode, struct file *file) {
printk(KERN_ALERT "\nOne instance of %s has been closed.\n", DEV_NAME);
return 0;
}
/* Next we'll see that that init_module
* registers the file_operations struct so the kernel will know
about the entry points therein
* gets back a major number
* calls pp_probe, to look for hardware resources
Had hardware resources been found, they would need to be allocated
for use by this driver, probably within the scope of init_module.
*/
int init_module(void) {
Major = register_chrdev( 0, DEV_NAME, &Fops);
if (Major < 0) {
printk("Registration Failure!\n");
return Major;
}
if (pp_probe() < 0) {
unregister_chrdev(Major, DEV_NAME);
printk(KERN_ALERT "pp_probe() failure!\n");
return -1;
}
printk(KERN_ALERT "\nRegistered %s, at major number = %d.\n\n", DEV_NAME, Major);
printk("To use %s, you must create a device file.\n", DEV_NAME);
printk("If this has not already been done, then enter:\n");
printk(" mknod /dev/%s c %d 0\n\n", DEV_NAME, Major);
printk("Also set appropriate permissions for /dev/%s.\n\n", DEV_NAME);
return 0;
}
/* The cleanup_module function unregisters the driver and, in a
'real' driver would free up any resources allocated by
init_module.
*/
void cleanup_module(void) {
int ret;
ret = unregister_chrdev(Major, DEV_NAME);
if (ret < 0)
printk(KERN_ALERT "\nUnregistration problem where ret = %d\n\n", ret);
else
printk(KERN_ALERT "\nUnregistered %s, at major number = %d\n\n", DEV_NAME, Major);
}
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#define DEVICE "/dev/pp"
int main() {
int ddfd = 0;
int ret = 0;
ddfd = open(DEVICE, O_RDWR);
if (ddfd < 0) {
printf("\nOpen of %s failed.\n", DEVICE);
exit(-1);
}
printf("\nOpen of %s succeeded.\n", DEVICE);
ret = close(ddfd);
if (ret < 0) {
printf("\nClosing %s failed.\n", DEVICE);
exit(-1);
}
printf("\n Close of %s succeeded.\n", DEVICE);
exit(0);
}
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define MEM_SZ 4096
struct shared_use_st {
int writ_by_you;
char some_text[BUFSIZ];
};
int main() {
int run = 1;
void *shared_mem = (void *) 0;
struct shared_use_st *shared_stuff;
char buffer[BUFSIZ];
int shmid;
shmid = shmget( (key_t)1234, MEM_SZ, 0666 | IPC_CREAT);
if (shmid == -1) {
perror("shmget in shrmem1_sysV failed");
exit(EXIT_FAILURE);
}
shared_mem = shmat(shmid, (void *)0, 0);
if (shared_mem == (void *)-1) {
perror("shmat in shrmem1_sysV failed");
exit(EXIT_FAILURE);
}
printf("memory attached at %X\n", (int)shared_mem);
shared_stuff = (struct shared_use_st *)shared_mem;
while (run) {
while (shared_stuff->writ_by_you == 1) {
sleep(3);
printf("Waiting for client ...\n");
}
printf("Enter some text: ");
fgets(buffer, BUFSIZ, stdin);
strcpy(shared_stuff->some_text, buffer);
shared_stuff->writ_by_you = 1;
if (strncmp(buffer, "end", 3) == 0) {
run = 0;
}
}
if (shmdt(shared_mem) == -1) {
perror("shmdt in shrmem1_sysV failed");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
shrmem2_sysV.c
/* sysV IPC shared memory - read from shared memory
shrmem2_sysV.c
meant to be used with shrmem1_sysV:
start shrmem2_sysV in background,
then start shrmem1_sysV
*/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define MEM_SZ 4096
struct shared_use_st {
int writ_by_you;
char some_text[BUFSIZ];
};
int main() {
int run = 1;
void *shared_mem = (void *) 0;
struct shared_use_st *shared_stuff;
int shmid;
shmid = shmget( (key_t)1234, MEM_SZ, 0666 | IPC_CREAT);
if (shmid == -1) {
perror("shmget in shrmem2_sysV failed");
exit(EXIT_FAILURE);
}
shared_mem = shmat(shmid, (void *)0, 0);
if (shared_mem == (void *)-1) {
perror("shmat in shrmem2_sysV failed");
exit(EXIT_FAILURE);
}
printf("memory attached at %X\n", (int)shared_mem);
shared_stuff = (struct shared_use_st *)shared_mem;
shared_stuff->writ_by_you == 0;
while (run) {
if (shared_stuff->writ_by_you == 1) {
printf("You_wrote: %s", shared_stuff->some_text);
sleep(rand() % 4);
shared_stuff->writ_by_you = 0;
if (strncmp(shared_stuff->some_text, "end", 3) == 0) {
run = 0;
}
}
}
if (shmdt(shared_mem) == -1) {
perror("shmdt in shrmem2_sysV failed");
exit(EXIT_FAILURE);
}
if (shmctl(shmid, IPC_RMID, 0) == -1) {
perror("shmctl in shrmem2_sysV failed");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define BUF 1024
struct my_msg_st {
long int my_msg_type;
char some_text[BUFSIZ];
};
int main() {
int run = 1;
struct my_msg_st some_data;
int msqid;
char buffer[BUF];
msqid = msgget( (key_t)1234, 0666 | IPC_CREAT);
if (msqid == -1) {
perror("msgget in sendmsg_sysV failed");
exit(EXIT_FAILURE);
}
while (run) {
printf("Enter some text:");
fgets(buffer, BUF, stdin);
some_data.my_msg_type = 1;
strcpy(some_data.some_text, buffer);
if (msgsnd(msqid, &some_data, BUF, 0) == -1) {
perror("msgsnd in sendmsg_sysV failed");
exit(EXIT_FAILURE);
}
if (strncmp(buffer, "end", 3) == 0) {
run = 0;
}
}
exit(EXIT_SUCCESS);
}
recmsg_sysV.c
/* sysV IPC message passing - receiver
recvmsg_sysV.c
meant to work with sendmsg_sysV:
start recvmsg_sysV in background,
then start sendmsg_sysV
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
struct my_msg_st {
long int my_msg_type;
char some_text[BUFSIZ];
};
int main() {
int run = 1;
struct my_msg_st some_data;
int msqid;
long int msg_to_recv = 1;
msqid = msgget( (key_t)1234, 0666 | IPC_CREAT);
if (msqid == -1) {
perror("msgget in recvmsg_sysV failed");
exit(EXIT_FAILURE);
}
while (run) {
if (msgrcv(msqid, &some_data, BUFSIZ, msg_to_recv, 0) == -1) {
perror("msgrcv in recvmsg_sysV failed");
exit(EXIT_FAILURE);
}
printf("You wrote: %s", some_data.some_text);
if (strncmp(some_data.some_text, "end", 3) == 0) {
run = 0;
}
}
if (msgctl(msqid, IPC_RMID, 0) == -1) {
perror("msgctl in recvmsg_sysV failed");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
Mathew, Neil, Richard Stones Beginning Linux Programming. 2nd ed. Wrox Press, Inc., September 1999.
Rubini, Alessandro, Jonathan Corbert. Linux Device Drivers. 2nd ed. O'Reilly Associates, Incorporated, July 2001.
Russell, Rusty. "User Mode Linux HOWTO". Online. Internet. 18, June 2002. Available: user-mode-linux.sourceforge.net/UserModeLinux-HOWTO.html.
I am currently pursuing a graduate degree in Computer Science from Eastern
Washington University in Cheney, Washington, USA.