# 一個驅動程序的角色是提供機制,而不是策略 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"