# 一个驱动程序的角色是提供机制,而不是策略 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"