|
int register_chdev(unsigned int major, const char *, struct fle_operations *fops)
|
字符设备被注册成功后,内核把这两个字符设备加入到内核字符设备驱动表中。内核字符设备驱动表保留指向struct file_operations的一个数据指针。用户进程调用设备读写操作时,通过这个指针访问设备的操作函数, struct file_operations中的域大部分是指向函数的函数指针,指向用户自己编写的设备操作函数。
struct file_operations ed_ops ={
#ifdef LINUX_24
NULL,
#endif
NULL,
device_read,
device_write,
NULL,
NULL,
device_ioctl,
NULL,
device_open,
NULL,
device_release,
};
|
注意到Linux2.4.x和 Linux2.2.x内核中定义的struct file_operations是不一样的。device_read()、device_write()、device_ioctl()、 device_open()、device_release()就是需要用户自己定义的函数操作了,这几个函数是最基本的操作,如果需要设备驱动程序完成更复杂的任务,还必须编写其他struct file_operations中定义的操作。eddev_module_init()除了注册设备及其操作外,它还有初始化字符设备结构struct ed_device,分配内核缓存区所需要的空间的作用。在内核空间,分配内存空间的API函数是kmalloc()。
下面介绍一下字符设备的主要操作例程device_open()、device_release()、device_read()、devie_write()。字符设备文件操作结构ed_ops中定义的指向以上函数的函数指针的原形:
device_open: int(*open)(struct inode *,struct file *)
device_release: int (*release) (struct inode *, struct file *);
device_read: ssize_t (*read) (struct file *, char *, size_t, loff_t *);
device_write: ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
|
操作int device_open(struct inode *inode,struct file *file)是设备节点上的第一个操作,如果多个设备共享这一个操作函数,必须区分设备的设备号。我们使用inode->i_rdev >> 8 语句获得设备的主设备号,本文中的接收设备主设备号是200,发送设备号是201。每个字符设备的file>private_data指向打开设备时候使用的file结构,private_data实际上可以指向用户定义的任何结构,这里只指向我们自己定义的struct ed_device,用来保存字符设备的一些基本信息,比如设备名、内核缓存区等。
操作ssize_t device_read(struct file *file,char *buffer,size_t length, loff_t *offset)是读取设备数据的操作。device_read()结构如图4所示。
图4

从设备中读取数据(用户空间调用read()系统调用)的时候,需要从内核空间把数据拷贝到用户空间,copy_to_user()可完成此功能,它和 memcpy()此类函数有本质的区别,memcpy()不能完成不同用户空间数据的交换。如果需要数据临界区的保护,使用spin_lock()内核 API负责加锁,spin_unlock()负责解锁,防止数据污染。由于串口守候进程server需要不断轮询设备,以查询是否有数据可读,如果用户进程不处于休眠状态,在用户空间查看进程使用资源情况,发现server占用了很多CPU资源。所以我们改进device_read(),使之在内核中轮询,当发现当前设备没有数据可读取,那么就阻塞用户进程,使用内核API add_wait_queue()可完成此功能,这时候用户进程并没有占用很多CPU资源,而是处于休眠状态。当内核发现有数据可读的时候,调用 remove_wait_queue()即可唤醒等待进程,这段
代码如下:
DECLARE_WAITQUEUE(wait,current);
add_wait_queue(&edp->rwait,&wait);
for(;;){
set_current_state(TASK_INTERRUPTIBLE);
if ( file->f_flags & O_NONBLOCK)
break;
/*其他代码 */
if ( signal_pending(current))
break;
schedule();
}
set_current_state(TASK_RUNNING);
remove_wait_queue(&edp->rwait,&wait);
|
操作ssize_t device_write(struct file *file,const char *buffer, size_t length,loff_t *offset)向设备写入数据。拷贝数据的copy_from_user()和copy_to_user()的功能恰恰相反,它是从用户空间拷贝数据到内核空间,如图5所示。
图 5
编写伪网络设备驱动程序
伪网络驱动程序和字符设备驱动程序一样,也必须初始化和注册。网络驱动需记录其发送和接收数据量的统计信息,所以我们定义一个记录这些信息的数据结构。
|