Title: UNIXs grand illusion
1UNIXs grand illusion
- How Linux makes a hardware device appear to be a
file
2Basic char-driver components
Device-driver LKM layout
modules payload is a collection of
callback-functions having prescribed
prototypes
function
function
function
AND a package of
function-pointers
fops
. . .
init
the usual pair of module-administration
functions
registers the fops
exit
unregisters the fops
3Background
- To appreciate the considerations that have
motivated the over-all Linux drivers design
requires an understanding of how normal
application-programs get their access to services
that the operating system offers - This access is indirect through specially
protected interfaces (i.e., system calls)
usually implemented as library functions
4Standard File-I/O functions
- int open( char pathname, int flags, )
- int read( int fd, void buf, size_t count )
- int write( int fd, void buf, size_t count )
- int lseek( int fd, loff_t offset, int whence )
- int close( int fd )
- (and other less-often-used file-I/O functions)
5Our elfcheck.cpp example
include ltfcntl.hgt for open() include
ltstdio.hgt for perror(), printf() include
ltunistd.hgt for read(), close() include
ltstring.hgt for strncpy() char buf4 int
main( int argc, char argv ) if ( argc 1
) return -1 // command-line argument is
required int fd open( argv1, O_RDONLY ) //
open specified file if ( fd lt 0 ) perror(
argv1 ) return -1 // quit if open
failed int nbytes read( fd, buf, 4 ) // read
first 4 bytes if ( nbytes lt 0 ) perror(
argv1 ) return -1 // quit if read failed if
( strncmp( buf, \177ELF, 4 ) 0 ) // check
for ELF id printf( File \s\ has ELF
signature \n, argv1 ) else printf( File
\s\ is not an ELF file \n, argv1 ) close(
fd ) // close the file
6Special device files
- UNIX systems treat hardware-devices as special
files, so that familiar functions can be used by
application programmers to access devices (e.g.,
open, read, close) - But a System Administrator has to create these
device-files (in the /dev directory) - Or alternatively (as weve seen), an LKM could
create these necessary device-files
7UNIX man pages
- A convenient online guide to prototypes and
semantics of the C library functions - Example of usage
-
- man 2 open
8The open function
- include ltfcntl.hgt
- int open( char pathname, int flags, )
- Converts a pathname to a file-descriptor
- File-descriptor is a nonnegative integer
- Used as a file-ID in subsequent functions
- flags is a symbolic constant
- O_RDONLY, O_WRONLY, O_RDWR
9The close function
- include ltunistd.hgt
- int close( int fd )
- Breaks link between file and file-descriptor
- Returns 0 on success, or -1 if an error
10The write function
- include ltunistd.hgt
- int write( int fd, void buf, size_t count )
- Attempts to write up to count bytes
- Bytes are taken from buf memory-buffer
- Returns the number of bytes written
- Or returns -1 if some error occurred
- Return-value 0 means no data was written
11The read function
- include ltunistd.hgt
- int read( int fd, void buf, size_t count )
- Attempts to read up to count bytes
- Bytes are placed in buf memory-buffer
- Returns the number of bytes read
- Or returns -1 if some error occurred
- Return-value 0 means end-of-file
12Notes on read() and write()
- These functions have (as a side-effect) the
advancement of a file-pointer variable - They return a negative function-value of -1 if an
error occurs, indicating that no actual data
could be transferred otherwise, they return the
number of bytes read or written - The read() function normally does not return 0,
unless end-of-file is reached
13The lseek function
- include ltunistd.hgt
- off_t lseek( int fd, off_t offset, int whence )
- Modifies the file-pointer variable, based on the
value of whence - enum SEEK_SET, SEEK_CUR, SEEK_END
- Returns the new value of the file-pointer (or
returns -1 if any error occurred)
14Getting the size of a file
- For normal files, your application can find out
how many bytes belong to a file using the
lseek() function - int filesize lseek( fd, 0, SEEK_END )
- But afterward you need to rewind the file if
you want to read its data - lseek( fd, 0, SEEK_SET )
15Device knowledge
- Before you can write a device-driver, you must
understand how the hardware works - Usually this means you need to obtain the
programmer manual (from manufacturer) - Nowdays this can often be an obstacle
- But some equipment is standardized, or is well
understood (because of its simplicity)
16Our RTC example
- We previously learned how the Real-Time Clock
device can be accessed by module code, using the
inb() and outb() macros - So we can create a simple char driver that lets
application-programs treat the RTCs memory as if
it were in an ordinary file
outb( addr, 0x70 ) outb( data, 0x71 )
outb( addr, 0x70 ) data inb( 0x71 )
17How device-access works
Application program
user space (unprivileged)
call
int 0x80
ret
Standard runtime library
int 0x80
iret
iret
Operating system (software)
call
supervisor space (privileged)
ret
Device-driver module (software)
out
in
Physical peripheral device (hardware)
18Our cmosram.c driver
- We implement three callback functions
- llseek // sets file-pointers position
- read // inputs a byte from CMOS
- write // outputs a byte to CMOS
- We omit other callback functions, such as
- open // we leave this function-pointer NULL
- release // we leave this function-pointer NULL
- The kernel has its own default implementation
for any function with NULL as its pointer-value
19The fops syntax
- The GNU C-compiler supports a syntax for struct
field-initializations that lets you give your
field-values in any convenient order
struct file_operations my_fops
llseek my_llseek, write my_write,
read my_read,
20init and exit
- The modules initialization function has to take
care of registering the drivers fops - register_chrdev( major, devname, fops )
- Then the modules cleanup function must make sure
to unregister the drivers fops - unregister_chrdev( major, devname )
- (These are prototyped in ltlinux/fs.hgt)
21Our dump.cpp utility
- We have written an application that lets users
display the contents of any file in both
hexadecimal and ascii formats - It also works on device files!
- With our driver-module installed, you can use it
to view the CMOS memory-values - ./dump /dev/cmos
22Now for a useful char-driver
- We can create a character-mode driver for the
processors physical memory (i.e. ram) - (Our machines have 2-GB of physical ram)
- But another device-file is named /dev/ram so
ours will be /dev/dram (dynamic ram) - Weve picked 85 as its major ID-number
- Our SysAdmin setup a device-node using root
mknod /dev/dram c 85 0
232-GB RAM has zones
ZONE_HIGH
1024128-MB
2048-MB ( 2GB)
ZONE_NORMAL
896-MB
16-MB
ZONE_LOW
Installed physical memory
24Legacy DMA
- Various older devices rely on the PC/ATs DMA
controller to perform data-transfers - This chip could only use 24-bit addresses
- Only the lowest 16-megabytes of physical memory
are visible to these devices - 224 0x01000000 (16-megabytes)
- Linux tries to conserve its use of memory from
this ZONE_LOW region (so devices will find free
DMA memory if its needed)
25Normal memory zone
- This zone extends from 16MB to 896MB
- Linux uses the lower portion of this zone for an
important data-structure that tracks how all the
physical memory is being used - Its an array of records mem_map
- (We will soon be studying this structure)
- The remainder of ZONE_NORMAL is free for
dynamic allocations by the OS kernel
26 HIGH memory
- Linux traditionally tried to map as much
physical memory as possible into virtual
addresses allocated to the kernel-space - Before the days when systems had 1-GB or more of
installed memory, Linux could linearly map ALL of
the physical memory into the 1-GB virtual
kernel-region - 0xC0000000 0xFFFFFFFF
- But with 2-GB theres not enough room!
27The 896-MB limit
HIGH MEMORY
temporary mappings using kmap()
Installed ram (2-GB)
HIGH ADDRESSES
Kernel space (1-GB)
896-MB
896-MB
always mapped
application programs code and data goes here
Physical address-space
User space (3-GB)
A special pair of kernel-functions named
kmap() and kunmap() can be called by
device-drivers to temporarily map vacant areas
in the kernels high address-space to pages of
actual physical memory
Virtual address-space
28dram.c module-structure
- We will need three kernel header-files
- include ltlinux/module.hgt
- // for printk(), register_chrdev(),
unregister_chrdev() - include ltlinux/highmem.hgt
- // for kmap(), kunmap(), and num_physpages
- include ltasm/uaccess.hgt
- // for copy_to_user()
29Our dram_size global
- Our init_module() function will compute the
size of the installed physical memory - It will be stored in a global variable, so it can
be accessed by our driver methods - It is computed from a kernel global using the
PAGE_SIZE constant (4096 for x86) - dram_size num_physpages PAGE_SIZE
30major ID-number
- Our major device ID-number is needed when we
register our device-driver with the kernel
(during initialization) and later when we
unregister our device-driver (during the
cleanup procedure) -
- int my_major 85 // static ID-assignment
31Our file_operations
- Our dram device-driver does not need to
implement special methods for doing the
open(), write(), or release() operations
(the kernel default operations will suffice) - But we DO need to implement read() and
llseek() methods - Our llseek() code here is very standard
- But read() is specially crafted for DRAM
32Using our driver
- We have provided a development tool on the class
website (named fileview.cpp) which can be used
to display the contents of arbitrary files --
including device-files! - The data is shown in hex and ascii formats
- The arrow-keys can be used for navigation
- The enter-key allows an offset to be typed
- Keys b, w, d and q adjust data-widths
33In-class exercise 1
- Install the dram.ko device-driver module then
use fileview to browse the contents of the
processors physical memory - ./fileview /dev/dram
- Be sure to try the b, w, d, q, ltENTERgt
and ltESCAPEgt keys - Also try ./fileview /dev/cmos
34In-class exercise 2
- The read() and write() callback functions in our
cmosram.c device-driver only transfer a single
byte of data for each time called, so it takes
128 system-calls to read all of the RTC
storage-locations! - Can you improve this drivers efficiency, by
modifying its read() and write() functions, so
theyll transfer as many valid bytes as the
supplied buffers space could hold?