ldd3读书笔记
|
can be replaced with %pI4
- dprintf("SRC: %u.%u.%u.%u. Mask: %u.%u.%u.%u. Target: %u.%u.%u.%u.%s\n", - NIPQUAD(src_ipaddr), - NIPQUAD(arpinfo->smsk.s_addr), - NIPQUAD(arpinfo->src.s_addr), + dprintf("SRC: %pI4. Mask: %pI4. Target: %pI4.%s\n", + &src_ipaddr, + &arpinfo->smsk.s_addr, + &arpinfo->src.s_addr, arpinfo->invflags & ARPT_INV_SRCIP ? " (INV)" : "");
# 一个驱动程序的角色是提供机制,而不是策略 2010-11-15 09:26 小默
“一个驱动程序的角色是提供机制,而不是策略。”--ldd3
机制:提供什么能力
策略:如何使用这些能力
机制和策略由软件不同部分,或完全不同的软件实现。
比如第一次实习时:
我们这边负责写驱动,只关注实现什么功能,怎么实现这样功能,这是机制。我们可以直接在设备管理器中安装卸载,或者用命令行安装卸载使用等,随意,也就是开发过程完全不考虑策略。
等开发进行到了一定阶段,又招了另外一名同学负责界面,这是策略。用户怎么使用这个驱动,操作界面是怎样的,是由他来负责的。
不知道是不是这个意思O(∩_∩)O~~ 回复 更多评论 删除评论 修改评论
# makefile写法 2010-11-15 17:14 小默
对于单个.c文件的hello world例子:
obj-m := hello.o
从目标文件hello.o简历一个模块hello.ko
如果模块来自两个源文件file1.c和file2.c:
obj-m := module.o
module-objs := file1.o file2.o
上面的命令,必须在内核系统上下建立文中被调用
-----
在任意当前工作目录中,需要在makefile中指明源码树的路径。
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build //TODO 不是在源码树中么/(ㄒoㄒ)/~~
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
创建一个模块需要调用两次上面的makefile
第一次:没有设置变量KERNELRELEASE,通过已安装模块目录中的符号链接指回内核建立树;然后运行default中的make命令,第二次使用makefile
第二次:已经设置了变量KERNELRELEASE,直接obj-m := hello.o创建模块 回复 更多评论 删除评论 修改评论
# 加载和卸载模块 2010-11-15 17:57 小默
modprobe实现和insmod一样加载模块到内核的功能
不同的是,加载前会检查模块中是否有当前内核中没有定义的symbol,如果有,在模块搜索路径中寻找其它模块是否含有上面的symbol,有的话,自动加载关联模块
insmod对于这种情况,会报错unresolved symbols
查看当前加载模块:
lsmod
cat /proc/modules
回复 更多评论 删除评论 修改评论
# 如果你的模块需要输出符号给其他模块使用 2010-11-15 18:12 小默
EXPORT_SYMBOL(name);
EXPORT_SYMBOL_GPL(name); 回复 更多评论 删除评论 修改评论
# 模块参数 2010-11-15 18:40 小默
说10次hello,Mom
# insmod hellop howmany=10 whom="Mom"
--
static char *whom = "world"; //必须给默认值
static int howmany = 1;
module_param(howmany, int, S_IRUGO);
module_param(whom, charp, S_IRUGO);
--
S_IRUGO 允许所有人读
S_IRUGO | S_IWUSR 允许所有人读,允许root改变参数
如果参数被sysfs修改,不会通知模块。不要使参数可写,除非准备好检测参数改变。
--
数组参数:
module_param_array(name, type, num, perm); 回复 更多评论 删除评论 修改评论
# 设备主次编号 2010-11-16 13:24 小默
$ ls -l /dev
...
crw-rw---- 1 vcsa tty 7, 132 Nov 15 17:16 vcsa4
crw-rw---- 1 vcsa tty 7, 133 Nov 15 17:16 vcsa5
crw-rw---- 1 vcsa tty 7, 134 Nov 15 17:16 vcsa6
crw-rw---- 1 root root 10, 63 Nov 15 17:16 vga_arbiter
drwxr-xr-x 2 root root 80 Nov 15 17:16 vg_colorfulgreen
crw-rw-rw- 1 root root 1, 5 Nov 15 17:16 zero
...
输出第一列是c的是字符设备,第一列b块设备
修改日期前的两个数字。
第一个是主设备编号:标识设备相连的驱动
第二个是次设备编号:决定引用哪个设备
-------
设备编号的内部表示
dev_t 在<linux/types.h>中定义,32位,12位主编号,20位次编号。
获得一个dev_t的主或次编号:<linux/kdev_t.h>
MAJOR(dev_t dev);
MINOR(dev_t dev);
将主次编号转换成dev_t:
MKDEV(int major, int minor);
--------
分配和释放设备编号
建立一个字符驱动时,做的第一件事就是获取一个或多个设备编号使用:
<linux/fs.h>
int register_chrdev_region(dev_t first, unsigned int count, char *name);
first要分配的起始设备编号
count请求的连续设备编号的总数
name连接到这个编号范围的设备的名字,会出现在/proc/devices和sysfs中
成功返回0,出错返回负的错误码。
如果事先不知道使用哪个设备编号,使用下面函数,内核会分配一个主设备编号:
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
dev是一个输出参数,返回分配范围的第一个数
firstminor请求第一个要用的次编号,常是0
设备编号的释放:
void unregister_chrdev_region(dev_t first, unsigned int count); 回复 更多评论 删除评论 修改评论
# scull安装脚本 2010-11-16 13:57 小默
脚本scull_load:
1 #!/bin/sh
2 module="scull"
3 device="scull"
4 mode="664"
5
6 # invoke insmod with all arguments we got
7 # and use a pathname, as newer modutils don't look in. by default
8 /sbin/insmod ./$module.ko $* || exit 1 # 插入模块,使用获取的所有参数($*)
9
10 # remove stale nodes删除无效的节点,不能删除device0,device1,device2...阿。。TODO
11 rm -f /dev/${device}[0-3]
12
13 major=$(awk "\\$2==\"$module\" {print \\$1}" /proc/devices) #TODO 没有搞明白
14 mknod /dev/${device}0 c $major 0 #创建4个虚拟设备
15 mknod /dev/${device}1 c $major 1
16 mknod /dev/${device}2 c $major 2
17 mknod /dev/${device}3 c $major 3
18
19 # give appropriate group/permissions, and change the group.
20 # Not all distributions have staff, some have "wheel" instead. #TODO 神马意思?
21 group="staff"
22 grep -q '^staff:' /etc/group || group="wheel"
23 # 改变设备的组和模式。脚本必须以root运行,但设备使用可能需要其它用户写。
24 chgrp $group /dev/${device}[0-3]
25 chmod $mode /dev/${device}[0-3]
26
回复 更多评论 删除评论 修改评论
# file_operations结构 2010-11-16 15:24 小默
一些处理文件的回调函数
1 // init file_operations
2 struct file_operations scull_fops = {
3 .owner = THIS_MODULE,
4 .llseek = scull_llseek,
5 .read = scull_read,
6 .write = scull_write,
7 .ioctl = scull_ioctl,
8 .open = scull_open,
9 .release = scull_release,
10 }; 回复 更多评论 删除评论 修改评论
# 注册字符设备 2010-11-16 15:25 小默
12 // 使用struct scull_dev结构表示每个设备
13 // TODO 没有理解什么意思
14 struct scull_dev {
15 struct scull_qset *data; // pointer to first quantum set
16 int quantum; // the current quantum size
17 int qset; // the current array size
18 unsigned long sizee; //amount of data stored here
19 unsigned int access_key; // used by sculluid and scullpriv
20 struct semaphore sem; // matual exclusion semaphore
21 struct cdev cdev; // 字符设备结构
22 };
23
24 // 初始化struct cdev,并添加到系统中
25 static void scull_setup_cdev(struct scull_dev *dev, int index)
26 {
27 int err, devno = MKDEV(scull_major, scull_minor + index);
28
29 // TODO 初始化已经分配的结构. 不是很理解
30 // cdev结构嵌套在struct scull_dev中,必须调用cdev_init()来初始化cdev结构
31 cdev_init(&dev->cdev, &scull_fops);
32 dev->cdev.owner = THIS_MODULE;
33 dev->cdev.ops = &scull_fops;
34 // 添加到系统中
35 err = cdev_add(&dev->cdev, devno, 1);
36 if(err)
37 printk(KERN_NOTICE "Error %d adding scull%d", err, index);
38 }
回复 更多评论 删除评论 修改评论
# system-config-selinux 2010-11-27 02:54 小默
system-config-selinux
什么时候改了,汗 //TODO 回复 更多评论 删除评论 修改评论
# read & write 2010-11-30 22:12 小默
// 表示每个设备
struct scull_dev{
struct scull_qset *data; // pointer to first quantum set
int quantum; // the current quantum size - 当前量子和量子集大小
int qset; // the current array size - 每个内存区域为一个量子,数组为一个量子集
unsigned long size; // amount of data stored here
unsigned int access_key; // used by sculluid and scullpriv
struct semaphore sem; // mutual exclusion semaphore
struct cdev cdev; // char device structure
};
// 量子集,即一个内存区域的数组
struct scull_qset{
void **data;
struct scull_qset *next;
};
ssize_t scull_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct scull_dev *dev = file->private_data;
struct scull_qset *dptr; // 量子集中的第一个元素
int quantum = dev->quantum, qset = dev->qset; // 当前量子和量子集大小
int itemsize = quantum * qset; // listitem中的字节数=量子大小*量子集大小
int item, s_pos, q_pos, rset;
ssize_t retval = 0;
if(down_interruptible(&dev->sem)) // TODO
return -ERESTARTSYS;
if(*f_pos > dev->size)
goto out;
if(*f_pos + count > dev->size)
count = dev->size - *f_pos;
// 查找listitem, qset index, and 量子中的偏移量
item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum;
q_pos = rest % quantum;
// 遍历list到右侧
dptr = scull_follow(dev, item); // 量子集中的第一个元素
if(dptr == NULL || !dptr->data || !dptr->data[s_pos])
goto out;
// 只读取到这个量子的尾部
if(count > quantum - q_pos)
count = quantum - q_pos;
if(copy_to_user(buf, dptr->data[s_pos] + q_pos, count)){
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;
out:
up(&dev->sem);
return retval;
}
// 一次处理单个量子
ssize_t scull_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
struct scull_dev *dev = filp->private_data;
struct scull_qset *dptr;
int quantum = dev->quantum, qset = dev->qset;
int itemsize = quantum * qset;
int item, s_pos, q_pos, rest;
ssize_t retval = -ENOMEM; // value used in "goto out" statements
if(down_interruptible(&dev->sem))
return -ERESTARTSYS;
// 查找列表元素,qset index and 量子中的偏移量
item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum;
q_pos = rest % quantum;
// 遍历list到右侧
dptr = scull_follow(dev, item);
if(dptr == NULL):
goto out;
if(!dptr->data){
dptr->data = kmalloc(qset * sizeof(char), GPL_KERNEL);
if(!dptr->data)
goto out;
memset(dptr->data, 0, qset * sizeof(char *));
}
if(!dptr->data[s_pos]){
dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
if(!dptr->data[s_pos])
goto out;
}
// 只写到这个量子的结束
if(count > quantum-q_pos)
count = quantum - q_pos;
// 从用户空间拷贝一整段数据to from count
if(copy_from_user(dptr->data[s_pos]+q_pos, buf, count)){
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;
// 更新size
if(dev->size < *f_pos)
dev->size = *f_pos;
out:
up(&dev->sem);
return retval;
}
//-------------------------------
// read和write的"矢量"版本
// readv轮流读取指示的数量到每个缓存;writev收集每个缓存的内容到一起并且作为单个写操作送出它们。
// count参数告诉有多少iovec结构,这些结构由应用程序创建,但是内核在调用驱动之前拷贝它们到内核空间。
ssize_t (*readv)(struct file *filp, const struct iovec *iov, unsigned long count, loff_t *ppos);
ssize_t (*writev)(struct file *filp, const struct iovec *iov, unsigned long count, loff_t *ppos);
// iovec描述了一块要传送的数据
struct iovec{
void __user *iov_base; // 开始于iov_base(在用户空间)
__kernel_size_t iov_len; // 并有iov_len长
};
回复 更多评论 删除评论 修改评论
# 重定向控制台消息 2010-11-30 22:44 小默
// 重定向控制台消息
// 使用一个参数指定接收消息的控制台的编号
int main(int argc, char **argv)
{
char bytes[2] = {11, 0}; // 11 是 TIOCLINUX 的功能号
if(argc == 2) bytes[1] = atoi(argv[1]); // the chosen console
else{
fprintf(stderr, "%s: need a single arg\n", argv[0]);
exit(1);
}
// TIOCLINUX传递一个指向字节数组的指针作为参数,数组的第一个字节是一个数(需要指定的子命令)。
// 当子命令是11时,下一个字节指定虚拟控制台。
if(ioctl(STDIN_FILENO, TIOCLINUX, bytes)<0){ // use stdin
fprintf(stderr, "%s: ioctl(stdin, TIOCLINUX): %s\n", argv[0], stderror(errno));
exit(1);
}
exit(0);
}
回复 更多评论 删除评论 修改评论
# Implementing files in /proc 2010-12-04 00:42 小默
// 在proc里实现文件,在文件被读时产生数据。
// 当一个进程读你的/proc文件,内核分配了一页内存,驱动可以写入数据返回给用户空间。
// buf 写数据的缓冲区;start有关数据写在页中哪里;eof必须被驱动设置,表示写数据结束;data用来传递私有数据。
// 假定不会有必要产生超过一页的数据,并且因此忽略了start和offset值。
int scull_read_procmem(char *buf, char **start, off_t offset, int count, int *eof, void *data)
{
int i, j, len = 0;
int limit = count - 80; // Don't print more than this
for(i = 0; i < scull_nr_devs && len <= limit; i++){ // TODO scull_nr_devs ?
struct scull_dev *d = &scull_devices[i];
struct scull_qset *qs = d->data;
if(down_interruptible(&d->sem))
return -ERESTARTSYS;
// 设备号,量子集大小,量子大小,存储的数据量
len += sprintf(buf+len, "\nDevice %i: qset %i, q %i, sz %li\n", i, d->qset, d->quantum, d->size);
for(; qs && len <= limit; qs = qs->next){ //scan the list 遍历量子链表
// 元素地址、链表地址
len += sprintf(buf + len, " item at %p, qset at %p\n", qs, qs->data); // %p 显示一个指针
if(qs->data && !qs->next) // dump only the last item
for(j = 0; j < d->qset, j++){
if(qs->data[j])
len += sprintf(buf+len, "%4i: %8p\n", j, qs->data[j]);
}
}
up(&scull_devices[i]);
}
*eof = 1;
return len; // 返回实际在页中写了多少数据
}
/// 移除entry的一些问题
// 移除可能发生在文件正在被使用时。/proc入口没有owner,没有引用计数。
// 内核不检查注册的名字是否已经存在,可能会有多个entry使用相同名称。而且在访问和remove_proc_entry时,它们没有区别。。。悲剧。。。 回复 更多评论 删除评论 修改评论
# The seq_file interface 2010-12-04 10:56 小默
// 创建一个虚拟文件,遍历一串数据,这些数据必须返回用户空间。
// start, next, stop, show
// sfile 总被忽略;pos指从哪儿开始读,具体意义完全依赖于实现。
// seq_file典型的实现是遍历一感兴趣的数据序列,pos就用来指示序列中的下一个元素。
// 在scull中,pos简单地作为scull_array数组的索引。
// 原型
void *start(struct seq_file *sfile, loff_t *pos);
// 在scull中的实现
static void *scull_seq_start(struct seq_file *s, loff_t *pos)
{
if(*pos >= scull_nr_devs)
return NULL; // no more to read
return scull_devices + *pos; // 返回供迭代器使用的私有数据
}
// next把迭代器后挪一位,返回NULL表示没有更多数据了
// v:上一次start/next调用返回的迭代器 TODO ???返回的不是私有数据么?
// pos: 文件中的当前位置。
void *next(struct seq_file *sfile, void *v, loff_t *pos);
// scull的实现
static void *scull_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
(*pos)++;
if(*pos >= scull_nr_devs)
return NULL;
return scull_devices + *pos;
}
// 内核完成迭代器,调用stop清理
void stop(struct seq_file *sfile, void *v);
// scull没有要清理的东西,stop方法是空的
// start到stop期间不会有sleep或者非原子操作,可以放心的在start中获得信号量或自旋锁。整个调用序列都是原子的。天书啊 TODO ???
// 在start和stop期间,内核调用show输出迭代器v生成的数据到用户空间
int show(struct seq_file *sfile, void *v);
// 输出,等效于用户空间的printf。返回非0值表示缓冲满,输出的数据会被丢弃。不过大多数实现都忽略返回值。
int seq_sprintf(struct seq_file *sfile, const char *fmt, ...);
// 等效于用户空间的putc和puts
int seq_putc(struct seq_file *sfile, char c);
int seq_puts(struct seq_file *sfile, const char *s);
// 如果s中有esc中的数据,这些数据用8进制输出。常见的esc是"\t\n\\",用于保持空格,避免搞乱输出。
int seq_escape(struct seq_file *m, const char *s, const char *esc);
// scull中show实现
static int scull_seq_show(struct seq_file *s, void *v)
{
struct scull_dev *dev = (struct scull_dev *)v;
struct scull_qset *d;
int i;
if(down_interrutible(&dev->sem))
return -ERESTARTSYS;
seq_printf(s, "\nDevice %i: qset %i, q %i, sz %li\n",
(int)(dev - scull_devices), dev->qset,
dev->quantum, dev->size);
for(d = dev->data; d; d = d->next){ // 遍历链表
seq_printf(s, " item at %p, qset at %p\n", d, d->data);
if(d->data && !d->next) // dump only the last item
for(i = 0; i < dev->qset; i++){
if(d->data[i])
seq_printf(s, " %4i: %8p\n", i, d->data[i]);
}
}
up(&dev->sem);
return 0;
}
// 迭代器:指向scull_dev的一个指针,囧。。。
// 迭代器操作集
static struct seq_operations scull_seq_ops = {
.start = scull_seq_start,
.next = scull_seq_next,
.stop = scull_seq_stop,
.show = scull_seq_show
};
/// 用file_operations结构结构,实现内核read/seek文件的所有操作。
// 创建一个open方法,连接文件和seq_file操作 TODO 没看懂
static int scull_proc_open(struct inode *inode, struct file *file)
{
// seq_open 连接文件和上面定义的scull_seq_ops
return seq_open(file, &scull_seq_ops);
}
// file_operations结构
static struct fle_operations scull_proc_ops = {
.owner = THIS_MODULE,
.open = scull_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release
};
// 在/proc中创建设备
entry = create_proc_entry("scullseq", 0, NULL);
if(entry)
entry->proc_fops = &scull_proc_ops;
// create_proc_entry原型
struct proc_dir_entry *create_proc_entry(const char *name,
mode_t, mode,
struct proc_dir_entry *parent); 回复 更多评论 删除评论 修改评论
# strace命令 - debug 2010-12-04 14:12 小默
略 O(∩_∩)O~~ 回复 更多评论 删除评论 修改评论
# ldd3_4.5_Debugging System Faults 2010-12-05 14:46 小默
讲了两部分
一、system opps
空指针引用,或者局部变量赋值覆盖了原eip,导致页错误
二、系统挂起 // TODO 看得云里雾里的
死循环等引起
假挂起:鼠标键盘等外设没有响应了,系统实际正常。可以看时间。。。
插入schedule调用防止死循环,作用是允许其他进程从当前进程窃取时间。讲了些弊端,没看懂
sysrq:没看懂
回复 更多评论 删除评论 修改评论
# re: 盖楼 2010-12-26 06:13 小默
所谓原子操作,就是该操作绝不会在执行完毕前被任何其他任务或事件打断,也就说,它的最小的执行单位,不可能有比它更小的执行单位。
原子操作主要用于实现资源计数,很多引用计数(refcnt)就是通过原子操作实现的。
原子类型定义:
typedef struct
{
volatile int counter;
}
atomic_t;
volatile修饰字段告诉gcc不要对该类型的数据做优化处理,对它的访问都是对内存的访问,而不是对寄存器的访问。 回复 更多评论删除评论 修改评论
# spinlock_t 2010-12-26 16:24 小默
17 typedef struct {
18 volatile unsigned int lock;
19 #ifdef CONFIG_DEBUG_SPINLOCK
20 unsigned magic;
21 #endif
22 } spinlock_t; 回复 更多评论 <a id="AjaxHolder_Comments_CommentList_ctl24_DeleteLink"
3.4 字符设备注册
struct cdev <linux/cdev.h>
1. 分配并初始化一个cdev结构。
如果期望获得一个独立的cdev结构:
my_cdev -> ops = & my_fops;
如果想把cdev嵌入到自定义的设备结构中:
上面两种方法,都需要初始化owner成员为THIS_MODULE
2.把新建立的cdev结构告诉内核
int cdev_add( struct cdev * dev,
dev_t num, // 设备响应的第一个设备号
unsigned int count); // 关联到设备上的设备号数量
3.移除cdev结构
void cdev_del( struct cdev * dev);
scull中字符设备注册
static void scull_setup_cdev( struct scull_dev * dev, int index)
{
int err, devno = MKDEV(scull_major, scull_minor + index); // MKDEV 把主次设备号合成为一个dev_t结构
// 分配并初始化cdev结构。struct cdev内嵌在struct scull_dev中
cdev_init( & dev -> cdev, & scull_fops);
dev -> cdev.owner = THIS_MODULE;
dev -> cdev.ops = & scull_fops;
// 通知内核,立即生效
err = cdev_add ( & dev -> cdev, // struct cdev *p: the cdev structure for the device
devno, // dev_t dev: 第一个设备号
1 ); // unsigned count: 该设备连续次设备号的数目
/* Fail gracefully if need be */
if (err)
printk(KERN_NOTICE " Error %d adding scull%d " , err, index);
}
int register_chrdev(unsigned int major, const char * name, struct file_operations * fops);
int unregister_chrdev(unsigned int major, const char * name);
3.5 open 和 release
3.5.1 open 方法
open方法在驱动中用来做一些初始化准备工作。
在多数驱动中,open进行的工作:
- 检查设备特定的错误(device-not-ready等)
- 如果设备第一次打开,初始化设备
- 更新 f_op 指针,如果需要的话
- 给 filp->private_data 赋值
open原型:
如何确定打开的是哪个设备:
- inode中i_cdev成员,是之前建立的cdev结构。通常我们需要的不是cdev,而是包含cdev的scull_dev结构,这时使用
scull设备是全局和永久的,没有维护打开计数,不需要"如果设备第一次打开,初始化设备"的操作。 // TODO
scull_open:
int scull_open( struct inode * inode, struct file * filp)
{
struct scull_dev * dev; /* device information */
// 文件私有数据,设置成对应的scull_dev
dev = container_of(inode -> i_cdev, struct scull_dev, cdev);
filp -> private_data = dev; /* for other methods */
/* now trim to 0 the length of the device if open was write-only */
// 文件以只读模式打开时,截断为0
if ( (filp -> f_flags & O_ACCMODE) == O_WRONLY) {
if (down_interruptible( & dev -> sem)) // 申请新号量,可中断
return - ERESTARTSYS;
// 释放整个数据区
scull_trim(dev); /* ignore errors */
up( & dev -> sem); // 释放信号量
}
return 0 ; /* success */
}
3.5.2 release方法
release进行的工作:
- 释放open在file->private_data中申请的一切
- 在调用最后一个close时关闭设备
scull的基本形式没有需要关闭的设备。
scull_release:
{
return 0 ;
}
close 和 release
并不是每一个close系统调用,都会引起对release的调用。
内核对一个打开的file结构,维护一个引用计数。dup和fork都只递增现有file结构的引用计数。只有当引用计数为0时,对close的调用才引起对release的调用。
size_t count, loff_t *offp);
ssize_t write(struct file *filp, const char _ _user *buff,
size_t count, loff_t *offp);
const void *from,
unsigned long count);
unsigned long copy_from_user(void *to,
const void _ _user *from,
unsigned long count);
使用上面两个函数需要注意的一点:当用户空间地址不在内存中时,进程会投入睡眠等待内存调度,所以任何存取用户空间的函数必须是可重入的。
数据传送完成后需要修改*offp为当前的文件位置。
read参数:
读device时需要考虑的特殊情况:当前读位置大于设备大小
这种情况发生在:进程a在读设备,同时进程b写设备将长度截断为0。此时scull的read返回0表示已到文件尾(没有更多的数据可以读)
if (!dptr->data[s_pos]) { dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL); if (!dptr->data[s_pos]) goto out; }
假设在同一时刻,A和B两个进程试图独立地写同一个scull设备的相同偏移。两个进程同时到达if判断,此时sptr->data[s_pos]为NULL,两个进程都申请了一块内存赋值给dptr->data[s_pos]。dptr->data[s_pos]最后只会保留一个值,假设B赋值在A之后,A申请的内存就被丢掉无法再获得,从来产生内存泄露(memory leak)。
上面是一个竞争状态(race condition)的示例,产生竞争状态的原因是没有控制对 共享数据 的访问。
rwlock_t my_rwlock = RW_LOCK_UNLOCKED; /* Static way */
rwlock_t my_rwlock;rwlock_init(&my_rwlock); /* Dynamic way */
void read_lock(rwlock_t *lock);void read_lock_irqsave(rwlock_t *lock, unsigned long flags);void read_lock_irq(rwlock_t *lock);void read_lock_bh(rwlock_t *lock);
void read_unlock(rwlock_t *lock);void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);void read_unlock_irq(rwlock_t *lock);void read_unlock_bh(rwlock_t *lock);
void write_lock(rwlock_t *lock);void write_lock_irqsave(rwlock_t *lock, unsigned long flags);void write_lock_irq(rwlock_t *lock);void write_lock_bh(rwlock_t *lock);int write_trylock(rwlock_t *lock);
void write_unlock(rwlock_t *lock);void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);void write_unlock_irq(rwlock_t *lock);void write_unlock_bh(rwlock_t *lock);