CS3013 Project 3

Chat Device

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.


Index


Description

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.


Driver Organization

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:

(Note that these parameters are just to make it easier to grade, since others work fine, too).

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/chatdev
in 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.


Chat

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.


Hints

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.


Hand In

You must hand in the following:

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

Return to 3013 Home Page

Send all project questions to the TA mailing list.

Send all Fossil administrative questions to the Fossil mailing list.