Due date: Thursday, October 16th by 11:59pm
Note, the positively last day this project can be turned in is Friday, October 17th by 11:59pm! We need to have time to grade them before assigning final grades. Please plan accordingly.
You will design and implement a character device driver, called a chatdev (for "chat device") for a virtual device that gives you the capabilities of doing a chat session. The driver will be "virtual" in the sense that it will not be tied to a piece of particular hardware. Rather, it will appear as a device to the operating system and the user. You will implement your device driver as a loadable module, a convenient means of extending Linux functionality.
You will then implement a "chatroom" (or "chat") application that
uses your chatdev. In a chat session, a user can write messages to
the chatroom (in this case, the chatdev device) and read messages,
where the chatdev device takes care of all synchronization internally
(meaning the chat process does not need to use semaphores or other
synchronization primatives). Moreover, the chat process does not need
to even be aware of the internal chatroom structure. Instead, it just
writes new messages via a write()
system call and reads
all messages since the last read via the read()
system
call. Reading from the chatdev device will not block, even if there
are no new messages. Note, you do not need to implement
Readers-Writers functionality (all processes can be writers), but you
will should still use cli()
and sti()
for
synchronizing. You are free to start with your chat.c
from
that project. Your new chat.c
will probably be
considerably simpler.
The Linux kernel references a device driver by a major number and a
minor number. The major number of a device normally commonly
identifies the class of devices that the driver can manage. For
example, floppy disks have a major device number of 2, IDE hard disks
have a major number of 3, and parallel ports have a major number of 6.
The file linux/include/linux/major.h
provides a full list
of the major numbers (given here for reference only as you do not need
to modify this file for this project.)
The minor number is an 8-bit number that references a specific device of a particular class (major number). Thus, two floppy disks on a machine would have a major number of 2, with the first have a minor number of 0 and the second having a minor number of 1.
The kernel must be informed of the device's existence before using
it. When booted, the kernel creates a special file for each device in
the system in the /dev
directory. These files are
indicated by a name, typically, instead of a major number and
pre-pended by the minor number. For example, the "name" for the
device file for the first floppy drive on your computer is
/dev/fd0
. These special files are created with the
mknod
command:
mknod /dev/device_name device_type major_number minor_number
The device_name
parameter is the name of the file,
device_type
is either 'c' for character or 'b' for block.
For this project you will use the mknod
command (using
sudo
of course) to create a device file for your chatdev
device. You should choose:
You may also need to change the permissions on your device. In
particular, for this project a user should be able to read and write
to the device, so the device permissions need to be set up
accordingly. You can do this with the command sudo chmod uog+rw
/dev/chatdev
after doing the mknod
command above.
To verify that you did this command correctly, check that your device
looks similar to:
crw-rw-rw- 1 root root 150, 0 Feb 18 07:35 /dev/chatdevin terms of permissions.
You only need to do the above operations one time as the chatdev will persist until you delete it with the the "rm" command.
The interface to a device is designed to look the same as a file
system. In Linux, file systems have a specific set of instructions to
access them: seek, read, write, readdr, poll, io control, memory
map, open, flush, release, synch, asynch, check media, revalidate
and lock. In order to be access through the /dev
file system, a device must provide a function call for each of the
above file system operations. You do this through a variable of type
struct file_operations
(declared in
linux/include/linux/fs.h
):
struct file_operations { loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char *, size_t, loff_t *); ssize_t (*write) (struct file *, const char *, size_t, loff_t *); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *); int (*fasync) (int, struct file *, int); int (*check_media_change) (kdev_t dev); int (*revalidate) (kdev_t dev); int (*lock) (struct file *, int, struct file_lock *); };
A device driver needs to only define the functions that make sense for it. Thus, your chatdev device driver will primarily need to implement open(), read(), write() and close(), and the other functions can be NULL. Thus, at this point your module header may look roughly like:
#include <linux/kernel.h> #include <linux/module.h> #include <linux/fs.h> /* prototypes for below operations */ ssize_t chatdev_read(struct file *, char *, size_t, loff_t *); ... /* registration info */ struct file_operations chatdev_fops = { NULL, /* lseek */ chatdev_read, /* read all pending chat messages */ chatdev_write, /* write new chat message */ NULL, /* readdir */ NULL, /* poll */ NULL, /* ioctl */ NULL, /* mmap */ chatdev_open, /* open */ NULL, /* flush */ chatdev_close, /* release */ NULL, /* sync */ NULL, /* async */ NULL, /* check media change */ NULL, /* revalidate */ NULL, /* lock */ }; ... /* definitions for above operations below here */ ssize_t chatdev_read(struct file *fp, char *buff, size_t num, loff_t *off) { /* chatdev read stuff */ ... } ...
A device driver registers itself with the file you created via
mknod
(say, /dev/chatdev
) by using
register_chrdev()
(with its counterpart,
unregister_chrdev()
), declared in
linux/include/linux/wrapper.h
. For example, you may have
a call that looks like:
ret = module_register_chrdev(CHATDEV_MAJOR, CHATDEV_NAME, &chatdev_fops);(with
CHATDEV_MAJOR
and CHATDEV_NAME
defined
as appropriate for your chatdev device, namely '150' and 'chatdev'
respectively). As a loadable module, a device driver will typically
register itself in the init_module()
function, and
un-register itself in the cleanup_module()
function.
Now, the really fun part (in the small chance that the above stuff was not fun enough :-) ) ... writing an application to use your scribes!
You are to write a program that implements a simple "chat" program. Your program will allow multiple users to connect in a live chat session. Users will enter chat messages and have their messages appear on the screens of the other users until a user types "exit". The users will have to decide the number of users in the chat session and their "id's" before the session starts.
A sample chat session follows:
susan 162 fossil=>>chat susan 163 fossil=>>chat Hello. Enter your handle: Susan (Ctrl-C pressed) =>> Hi, Joe! Susan: Hi, Joe! (Ctrl-C pressed) =>> I've been playing Battlefield 1942 all night. Go Bazookamen! Joe: Boy, am I starvin'! Doing all that OS homework makes me hungry. Joe: I'm fixing to go to Micky D's! Susan: I've been playing Battlefield 1942 all night. Go Bazookamen! (Ctrl-C pressed) =>> Yeah, that prof Claypool is brutal. Can't wait until term break, when I can sleep until noon and only log on to play with my new chat program! (Ctrl-C pressed) =>> exit Goodbye.
Each chat process basically acts like a reader and reads from the
chatdev and displays new messages on the screen. When a user
wants to type in a new message, they hit Ctrl-C
and enter
a message. The chat process then acts like a writer and writes this
to the chatdev and goes back to reading and displaying
messages.
Pseudo-code for your chat program might look like:
begin main set up signal handler to enter messages while(1) { read from chatroom display any new messages sleep for 1 second } end begin signal handler get input if input is "exit" then cleanup and exit write input to chatroom end
See the Samples section on the course Web page for examples of how to use software signals.
For your chat program, it may be helpful to store messages with a structure similar to:
struct message { char text[MAX_LEN]; /* message */ }; struct chatroom { int index; /* spot to put next messages */ struct message msg[MAX_MSG]; /* array of messages */ };
Each process would then add a new message at index
,
incrementing it by one (wrapping it around to 0 when
MAX_MSG
was reached. Each process would keep a private
index of the last message that it has seen. If the private index was
less than the chatroom index, there would be more messages to
display.
You do not need to use shared memory in the kernel (in fact, using
the system calls for shared memory in the kernel does not make sense.)
Instead, your struct chatroom
(or equivalent) can simply
be declared as a global variable in your chatdev.c
module.
To keep track of what messages each process has read, you may use
the position field (f_pos
) in the file descriptor passed
into the read()
call. The struct file
is
defined in linux/fs.h
and looks like:
struct file { struct file *f_next, **f_pprev; struct dentry *f_dentry; struct file_operations *f_op; mode_t f_mode; loff_t f_pos; unsigned int f_count, f_flags; unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin; struct fown_struct f_owner; unsigned int f_uid, f_gid; int f_error; unsigned long f_version; void *private_data; };
If you make a device file (via the mknod
command) that
you do not want, you can just remove it using rm
(and
sudo
) as you would a normal file.
Remember, that accessing global system variables (such as a chat
data structures) can lead to race conditions! When you don't want the
OS to be interrupted, you can disable interrupts using the function
cli()
, which stands for "CLear Interupts flag." To
re-enable, use the function sti()
, for "SeT the
Interrupts flag." You need to use #include <asm/system.h>
with both instructions. Oh, and also note these functions can
only be called in the OS. They are not system calls (remember,
we would not want the user program to be able to enable and disable
interrupts at will!).
You might check out http://www.rivier.edu/faculty/amoreira/web/cs690b03/calbet/page1.html, as it describes how to write a Linux device driver.
You must hand in the following:
chatdev.c
and chat.c
).
The t (/cs/bin/turnin
) for proj3 is "proj3".
When turnin, also include file "group.txt" which contains the
following:
group_name login_name1 last_name1, first_name1 login_name2 last_name2, first_name2 ...
Also, before you use turnin tar
up (with
gzip
) your files. For example:
mkdir proj3 cp * proj3 /* copy all your files to submit to proj3 directory */ tar -czf proj3.tgz proj3
then copy your files from your Fossil client to your CCC account:
scp proj3.tgz login_name@ccc:~/ /* will ask your ccc passwd */ ssh login_name@ccc /* will ask your ccc passwd */ /cs/bin/turnin submit cs3013 proj3 proj3.tgz
Send all project questions to the TA mailing list.
Send all Fossil administrative questions to the Fossil mailing list.