/dev
directory
/dev/tty* terminals
/dev/fd* floppy fisk
/dev/mt* magnetic tape
/dev/st* streaming tape
/dev/mouse mouse
printf("the value of %s is %d",
str, n)
goes through these steps:
Frequently this layer insulates itself from the device by using buffers in kernel space. The layer reads and writes to these buffers, and the device driver layer is responsible for filling or emptying these.
This set of operations depends on the device and cannot be generalised.
The process that is using the device can do one of two things: wait until the device completes (block) or continue asynchronously (non-blocking).
In either case, something must happen when the interrupt occurs. If the process has blocked, then it can be woken up. If the process continued then a signal should be sent.
Typical block devices are disks.
Additional operations on block devices will include
Such devices include terminals, mice, etc.
This information is maintained (in Unix) by information in the inode. A field labels the device type. See this by "ls -l" on files in /dev
crw-rw-rw- root 22, 0 fb
brw-rw-rw- root 16, 2 fd0
"fb" (frame buffer) is a character device, "fd0" is a block device. The
numbers "22, 0" are the major and minor device numbers respectively. These
identify which device driver it actually is.
When the device interrupts to say that data is ready to read, the data is placed on the incoming queue.
If buffering was not used, then the reading process would have to be ready to process it immediately and finish processing it before the next character arrived. Typically, the input device would be much slower than the process. What should the process do?
If it is blocked, then on each new arrival, it would have to be the active process. This would involve a context switch on every character read. If it is "busy waiting", then time would be wasted.
On a multi-tasking system either way wastes time. So the norm on multi-tasking systems is to buffer character I/O. MSDOS would not need to buffer though.
Who should do this? If the process should do it, then it would have to be aware of each character as it was typed. This would require unbuffered input.
The device driver could do it, because it needs to handle characters as they arrive anyway. In this case the terminal driver does input mode processing.
If the device driver does this processing, then the process ultimately reading from the device would not want to see the original data but the processed data.
The terminal drivers would maintain two input queues: the one containing the raw data and the one containing the canonical data. There are three modes that the driver can be in:
The output side of this is that in cooked and rare modes the processing is done, but in raw mode no output processing is done.
Terminal devices are described by the C structure termios
#include <termios.h>
#include <unistd.h>
struct termios {
tcflag_t c_iflag; /* input modes */
tcflag_t c_oflag; /* output modes */
tcflag_t c_cflag; /* control modes */
tcflag_t c_lflag; /* local modes */
cc_t c_cc[NCCS]; /* control chars */
There is an (incomplete) set of functions to manipulate the contents
of this structure
#include
#include
int tcgetattr(int fd, struct termios *termios_p);
int tcsetattr(int fd, int optional_actions, struct termios *termios_p);
int cfmakeraw(struct termios *termios_p);
speed_t cfgetospeed(struct termios *termios_p);
int cfsetospeed(struct termios *termios_p, speed_t speed);
speed_t cfgetispeed(struct termios *termios_p);
int cfsetispeed(struct termios *termios_p, speed_t speed);
A typical program will use tcgetattr()
on an opened
serial port to get the terminal attributes.
struct termios term_info;
int fd;
fd = open("/dev/ttyS0", O_RDWR);
tcgetattr(fd, &term_info);
(NB we have omitted error handling code here).
A typical program will then make changes to
the termios
structure and then set these changes in
the serial port by tcsetattr()
.
The second parameter to this call controls when the action takes place.
For example, TCSANOW
means that the change takes place
immediately, and applies to all bytes in the input and output buffers.
There are convenience functions to get and change serial line speeds, such
as cfsetospeed()
to set the output port speed. These
functions act on the termios
structure, and do not affect
the device until tcsetattr()
is called.
cfsetispeed(&term_info, B2400);
cfsetospeed(&term_info, B2400);
tcsetattr(fd, TCSANOW, &term_info);
The cfmakeraw()
function primarily affects the input processing
mode, by setting it into raw mode.
At the end of processing, a well-behaved progam will reset the old values in the serial port.
There is also a set of functions to manipulate a terminal port once opened as a file
#include
#include
int tcsendbreak(int fd, int duration);
int tcdrain(int fd);
int tcflush(int fd, int queue_selector);
int tcflow(int fd, int action);
pid_t tcgetpgrp(int fd);
int tcsetpgrp(int fd, pid_t pgrpid);
To communicate between a computer using the serial port and another device at the other end of the serial cable, there has to be agreement about a number of factors
B1200
, B9600
, etc)
CS7
, CS8
, etc).
These need to be set in the c_cflag
field
of the termios
structure
CRTSCTS
is set or
unset in the c_cflag
field
term_info.c_cflag |= CRTSCTS | CS8;
Typical Unix I/O will block until something is ready for reading or writing. It does not have a timeout mechanism, and so can block for hours if I/O devices are not ready. The serial line driver can be set to
c_cc[]
array.
For example, this sets a timeout of 15 seconds if no characters have
been read
term_info.c_cc[VTIME] = 150; // 15 seconds
term_info.c_cc[VMIN] = 0;
Here is a C program that reads from the serial port
#include <termios.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#define LEN 512
int main(int argc, char **argv) {
struct termios term_info;
struct termios old_term_info;
int fd;
int old_out_baud_rate;
char str[LEN];
int nread;
fd_set rfds;
if ((fd = open("/dev/ttyS0", O_RDWR)) < 0) {
perror("Can't open serial port");
exit(1);
}
if (tcgetattr(fd, &term_info) == -1) {
perror("Can't get serial port attributes");
close(fd);
exit(2);
}
old_term_info = term_info;
// set h/w control
term_info.c_cflag |= CRTSCTS | CS8;
if (term_info.c_lflag & ICANON) {
fprintf(stderr, "In canonical mode");
term_info.c_lflag &= ~ICANON;
}
// set timeout
term_info.c_cc[VTIME] = 150; // 15 seconds
term_info.c_cc[VMIN] = 0;
cfsetispeed(&term_info, B2400);
cfsetospeed(&term_info, B2400);
if (tcsetattr(fd, TCSANOW, &term_info) == -1) {
perror("Can't set serial port attributes");
close(fd);
exit(3);
}
while ((nread = read(fd, str, LEN)) > 0) {
fprintf(stderr, "Read %d %d\n", nread, str[0]);
/* write(1, str, nread); */
}
/*
* reset state
*/
if (tcsetattr(fd, TCSANOW, &old_term_info) == -1) {
perror("Can't reset serial port attributes");
close(fd);
exit(4);
}
close(fd);
exit(0);
}