全部博文(146)
分类: LINUX
2008-01-02 17:36:38
UTS_RELEASE
这个宏定义扩展成字符串, 描述了这个内核树的版本. 例如, "2.6.10".
LINUX_VERSION_CODE
这个宏定义扩展成内核版本的二进制形式, 版本号发行号的每个部分用一个字节表示. 例如, 2.6.10 的编码是 132618 ( 就是, 0x02060a ). []有了这个信息, 你可以(几乎是)容易地决定你在处理的内核版本.
KERNEL_VERSION(major,minor,release)
这个宏定义用来建立一个整型版本编码, 从组成一个版本号的单个数字. 例如, KERNEL_VERSION(2.6.10) 扩展成 132618. 这个宏定义非常有用, 当你需要比较当前版本和一个已知的检查点.
# If KERNELRELEASE is defined, we've been invoked from the
# kernel build system and can use its language.
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
# Otherwise we were called directly from the command
# line; invoke the kernel build system.
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modulesendif
gdb /usr/src/linux/vmlinux /proc/kcore
第一个参数是非压缩的 ELF 内核可执行文件的名子, 不是 zImage 或者 bzImage
第二个参数是核心文件的名子.
Linux 可加载模块是 ELF 格式的可执行映象;ELF被分成几个sections. 其中有 3 个典型的sections与调试会话相关:
.text
这个节包含有模块的可执行代码. 调试器必须知道在哪里以便能够给出回溯或者设置断点.
.bss
在编译时不初始化的任何变量在 .bss 中
.data
在编译时需要初始化的任何变量在 .data 里.
为了gdb能够调试可加载模块需要通知调试器一个给定模块的各个sections加载在哪里. 这个信息在 /sys/module/module_name/sections下. 包含名子为 .text , .bss, .data等文件; 每个文件的内容是那个section的基地址.
gdb的add-symbol-file命令用来加载模块相关信息
add-symbol-file 模块名 text所在的基地址 -s .bss bss所在基地址 -s .data data所在基地址
add-symbol-file ../scull.ko 0xd0832000 -s .bss 0xd0837100 -s .data 0xd0836be0
struct list_head todo_list;
...
void todo_add_entry(struct todo_struct *new)
{
struct list_head *ptr;
struct todo_struct *entry;
list_for_each(ptr, &todo_list)
{
entry = list_entry(ptr, struct todo_struct, list);
if (entry->priority < new->priority) {
list_add_tail(&new->list, ptr);
return;
}
}
list_add_tail(&new->list, &todo_struct)
}
module_init(initialization_function): 声明模块初始化函数
module_exit(cleanup_function): 声明模块注销函数
EXPORT_SYMBOL(name): 声明符号在模块外可用
EXPORT_SYMBOL_GPL(name): 声明符号仅对使用 GPL 许可的模块可用.
在模块中声明如下:static char *whom = "world";
static int howmany = 1;
module_param(howmany, int, S_IRUGO);
module_param(whom, charp, S_IRUGO);调用时如下:insmod moduel_name howmany=10 whom="Mom"
比如:
struct test{int a; int b; int c; inte}test_t;
&test_t == container_of(&(test_t.c), struct test, c)
__setup("test=", test_setup);
void *kmalloc(size_t size, int flags); 试图分配 size 字节的内存; 返回值是指向那个内存的指针或者如果分配失败为NULL. flags 参数用来描述内存应当如何分配
申请的空间大小限制: 大概为128K
void kfree(void *ptr);分配的内存应当用 kfree 来释放. 传递一个 NULL 指针给 kfree 是合法的.
get_free_page申请的page数限制: 2^MAX_ORDER, 2的MAX_ORDER次方个page. 通常MAX_ORDER=10, 也就是最多2^10=1024个page, 4Mbyte
int access_ok(int type, const void *addr, unsigned long size): 验证用户空间有效性
put_user(datum, ptr)
__put_user(datum, ptr)
get_user(local, ptr)
__get_user(local, ptr)
如果使用上述四个函数时, 发现一个来自编译器的奇怪消息, 例如"coversion to non-scalar type requested". 必须使用 copy_to_user 或者 copy_from_user.
unsigned long copy_to_user(void __user *to,const void *from,unsigned long count); 拷贝一整段数据到用户地址空间. 任何存取用户空间的函数必须是可重入的. 此函数可能导致睡眠
unsigned long copy_from_user(void *to,const void __user *from,unsigned long count); 从用户地址空间拷贝一整段数据. 任何存取用户空间的函数必须是可重入的. 此函数可能导致睡眠
int capable(int capability);
int printk(const char * fmt, ...);向console(而不是虚拟终端)打印一条消息, 并通过附加不同的记录级别或者优先级在消息上对消息的严重程度分类.没有指定优先级的printk语句缺省是DEFAULT_MESSAGE_LOGLEVEL, 在 kernel/printk.c里指定作为一个整数. 在2.6.10内核中, DEFAULT_MESSAGE_LOGLEVEL是KERN_WARNING, 但这个值在不同的内核中可能不一样.
用于紧急消息, 常常是那些崩溃前的消息.
需要立刻动作的情形.
严重情况, 常常与严重的硬件或者软件失效有关.
用来报告错误情况; 设备驱动常常使用 KERN_ERR 来报告硬件故障.
有问题的情况的警告, 这些情况自己不会引起系统的严重问题.
正常情况, 但是仍然值得注意. 在这个级别一些安全相关的情况会报告.
信息型消息. 在这个级别, 很多驱动在启动时打印它们发现的硬件的信息.
用作调试消息.
printk(KERN_INFO "hello, world\n");//注意:消息优先级与正文内容之间没有逗号
int printk_ratelimit(void); 在你认为打印一个可能会常常重复的消息之前调用来避免重复输出很多相同的调试信息. 如果这个函数返回非零值, 继续打印你的消息, 否则跳过打印.
使用举例
if (printk_ratelimit())
printk(KERN_NOTICE "The printer is still on fire\n");
int print_dev_t(char *buffer, dev_t dev);
char *format_dev_t(char *buffer, dev_t dev);
current->state = TASK_INTERRUPTIBLE
MKDEV(int major, int minor): 讲主次设备号转换成dev_t
int register_chrdev_region(dev_t first, unsigned int count, char *name): 获取一个或多个设备编号来使用
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);动态分配一个主编号
void unregister_chrdev_region(dev_t first, unsigned int count); 释放设备编号
设备注册:
struct cdev *cdev_alloc(void);为struct cdev申请内存空间
void cdev_init(struct cdev *cdev, struct file_operations *fops);初始化struct cdev结构. 其成员owner应当设置为 THIS_MODULE
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);将设备注册到内核
dev 是struct cdev结构
num 是这个设备响应的第一个设备号
count 是应当关联到设备的设备号的数目. 常常 count 是 1, 但是有多个设备号对应于一个特定的设备的情形.
void cdev_del(struct cdev *dev);将设备注销
major和name 必须和传递给register_chrdev的相同, 否则调用会失败.
devfs_handle_t devfs_register (devfs_handle_t dir,
const char *name,
unsigned int flags,
unsigned int major, unsigned int minor,
umode_t mode,
void *ops, void *info);创建设备节点
dir:需要创建的设备文件所在目录,默认为/dev
name: 需要创建的设备文件名
flags: 通常取DEVFS_FL_DEFAULT
major: 主设备号
minor: 次设备号
mode: 此设备文件的读写权限
ops: 此设备的file_operations结构
info:
#define DEV_ID ((void*)123456)
#define DEV_NAME "XXXXXXXXXXXXXX"
#define DEV_MAJOR 200
#define DEV_IRQ IRQ_XXXX
#define DEV_IRQ_MODE SA_SHIRQ
...
//regist char device
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
ret = register_chrdev(DEV_MAJOR, DEV_NAME, &fops);
#else
cdev_init(&dev_char, &fops);
dev_char.owner = THIS_MODULE;
dev_char.ops = &fops;
ret = cdev_add(&dev_char, MKDEV(DEV_MAJOR, 0), 1);
#endif
if (ret < 0)
goto __mod_init_err1;
//make devfs
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
devfs_handle = devfs_register(NULL, DEV_NAME, DEVFS_FL_DEFAULT,
DEV_MAJOR, 0, S_IFCHR | S_IRUSR | S_IWUSR, &fops, NULL);
if (NULL == devfs_handle)
{
ret = -1;
goto __mod_init_err2;
}
#else
dev_class = class_create(THIS_MODULE, DEV_NAME);
if(IS_ERR(dev_class))
{
ret = PTR_ERR(dev_class);
goto __mod_init_err2;
}
class_device_create(dev_class, MKDEV(DEV_MAJOR, 0), NULL, DEV_NAME);
ret = devfs_mk_cdev(MKDEV(DEV_MAJOR, 0), S_IFCHR | S_IRUGO | S_IWUSR, DEV_NAME);
if(ret)
goto __mod_init_err3;
#endif
......
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
__mod_init_err3:
class_device_destroy(dev_class, MKDEV(DEV_MAJOR, 0));
class_destroy(dev_class);
#endif
__mod_init_err2:
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
unregister_chrdev(DEV_MAJOR, DEV_NAME);
#else
cdev_del(&dev_char);
#endif
__mod_init_err1:
free_irq(DEV_IRQ, DEV_ID);
#endif//end of "ifndef INPUT_DEVICE"
__mod_init_err0:
return ret;
如果值为负值表示有一个错误. 这个值指出了什么错误, 根据
ssize_t write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
1. 通常应当更新 *offp 中的文件位置来表示在系统调用成功完成后当前的文件位置.
2. 返回值:
如果值等于 count, 要求的字节数已被传送
如果正值, 但是小于 count, 只有部分数据被传送
如果值为 0, 什么没有写. 这个结果不是一个错误
一个负值表示发生一个错误
static unsigned int scull_p_poll(struct file *filp, poll_table *wait)
{
struct scull_pipe *dev = filp->private_data;
unsigned int mask = 0;
/*
* The buffer is circular; it is considered full
* if "wp" is right behind "rp" and empty if the
* two are equal.
*/
down(&dev->sem);
poll_wait(filp, &dev->inq, wait);
poll_wait(filp, &dev->outq, wait);
if (dev->rp != dev->wp)
mask |= POLLIN | POLLRDNORM; /* readable */
if (spacefree(dev))
mask |= POLLOUT | POLLWRNORM; /* writable */
up(&dev->sem);
return mask;
}
这个代码简单地增加了 2 个 scullpipe 等待队列到 poll_table, 接着设置正确的掩码位, 根据数据是否可以读或写.
signal(SIGIO, &input_handler); /* dummy sample; sigaction() is better */
fcntl(STDIN_FILENO, F_SETOWN, getpid());
oflags = fcntl(STDIN_FILENO, F_GETFL); //get original setting
fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC);
struct fasync_struct *async_queue = NULL;
static int fasync(int fd, struct file *filp, int mode)
{
return fasync_helper(fd, filp, mode, &async_queue);
}
当数据到达, 用下面的语句来通知异步读者.
if (async_queue)
kill_fasync(&async_queue, SIGIO, POLL_IN);
在release方法中应该调用
/* remove this filp from the asynchronously notified filp's */
fasync(-1, filp, 0);
loff_t scull_llseek(struct file *filp, loff_t off, int whence)
{
struct scull_dev *dev = filp->private_data;
loff_t newpos;
switch(whence)
{
case 0: /* SEEK_SET */
newpos = off;
break;
case 1: /* SEEK_CUR */
newpos = filp->f_pos + off;
break;
case 2: /* SEEK_END */
newpos = dev->size + off;
break;
default: /* can't happen */
return -EINVAL;
}
if (newpos < 0)
return -EINVAL;
filp->f_pos = newpos;
return newpos;
}
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset)
mmap操作: 将设备驱动里的一段内存映射到用户空间. 通过在current->mm中增加具有物理地址->虚拟地址映射关系的pmd, pte项来实现
mmap的设备驱动中的原型为
int (*mmap) (struct file *filp, struct vm_area_struct *vma);
static int simple_remap_mmap(struct file *filp, struct vm_area_struct *vma)
{
if (remap_pfn_range(vma, vma->vm_start, vm->vm_pgoff, vma->vm_end - vma->vm_start, vma->vm_page_prot))
return -EAGAIN;
vma->vm_ops = &simple_remap_vm_ops;
simple_vma_open(vma);
return 0;
}
static int simple_nopage_mmap(struct file *filp, struct vm_area_struct *vma)
{
unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
if (offset >= __pa(high_memory) || (filp->f_flags & O_SYNC))
vma->vm_flags |= VM_IO;
vma->vm_flags |= VM_RESERVED;
vma->vm_ops = &simple_nopage_vm_ops;
simple_vma_open(vma);
return 0;
}
struct page *simple_vma_nopage(struct vm_area_struct *vma, unsigned long address, int *type)如果由于某些原因, 不能返回一个正常的页(即请求的地址超出驱动的内存区), 可以返回NOPAGE_SIGBUS指示错误; 也可以返回NOPAGE_OOM来指示由于资源限制导致的失败.
{
struct page *pageptr;
unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
unsigned long physaddr = address - vma->vm_start + offset;
unsigned long pageframe = physaddr >> PAGE_SHIFT;
if (!pfn_valid(pageframe))
return NOPAGE_SIGBUS;
pageptr = pfn_to_page(pageframe);
get_page(pageptr);
if (type)
*type = VM_FAULT_MINOR;
return pageptr;
}
这里,
get_page是增加此页面的引用计数,必须实现
type是返回错误类型, 对设备驱动来说, VM_FAULT_MINOR是唯一正确的值
.....
#elif defined(__arm__)
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
/* This is an IO map - tell maydump to skip this VMA */
vma->vm_flags |= VM_IO;
#elif defined(__sh__)
.....
它允许一个在等待一个semaphore的用户空间进程被用户中断. 作为一个通用的规则, 不应该使用不可中断的操作, 除非实在是没有选择. 不可中断操作将创建不可杀死的进程.
如果操作是可中断的, 函数返回一个非零值, 并且调用者不持有semaphore. 正确的使用 down_interruptible 需要一直检查返回值并且针对性地响应.
举例:
DECLARE_MUTEX(mutex);
...
if (down_interruptible(&mutex))
return -ERESTARTSYS;
int down_trylock(struct semaphore *sem);
如果在调用down_trylock时semaphore不可用, 它将立刻返回一个非零值.
void up(struct semaphore *sem);
释放semaphore
void init_rwsem(struct rw_semaphore *sem);
void down_read(struct rw_semaphore *sem);
int down_read_trylock(struct rw_semaphore *sem);
void up_read(struct rw_semaphore *sem);
void down_write(struct rw_semaphore *sem);
int down_write_trylock(struct rw_semaphore *sem);
void up_write(struct rw_semaphore *sem);
void downgrade_write(struct rw_semaphore *sem);
这一系列函数基本与mutex对应版本同. 区别是, 可以有多个进程同时拥有读锁, 但仅允许一个进程拥有写锁
另外一个特性: 如果有进程尝试写锁定后(即使没有拥有写锁),所有其他尝试读锁定的进程都将等待,直到写锁定解除.
completion
DECLARE_COMPLETION(name)
定义并初始化一个completion
INIT_COMPLETION(struct completion c);
重新初始化一个completion, 主要是用在被唤醒的进程重新进入等待前的初始化
void init_completion(struct completion *c)
初始化一个completion
void wait_for_completion(struct completion *c)
进行一个不可打断的等待
void complete(struct completion *c)
唤醒一个等待的进程
void complete_call(struct completion *c)
唤醒所有等待的进程
void complete_and_exit(struct completion *c, long retval)
在内核线程A收到退出命令后,通知另一个内核线程B退出, 并等待B退出完成; B退出完成后调用complete通知A, 如果这种情况下用的是completion机制而A最后等待complete的时候调用的是这个函数,那么,A一收到B退出的通知就会结束整个线程
spin lock
spin lock是一个互斥设备, 只能有 2 个值:"上锁"和"解锁".
内核抢占(高优先级的进程抢占低优先级的进程)在持有spin lock期间被禁止
spinlock_t my_lock = SPIN_LOCK_UNLOCKED
编译时初始化
void spin_lock_init(spinlock_t *lock)
运行时初始化
void spin_lock(spinlock_t *lock)
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags)
void spin_lock_irq(spinlock_t *lock)
void spin_lock_bh(spinlock_t *lock)
加锁
spin_lock_irqsave在获得锁之前在当前处理器禁止中断, 之前的中断状态会保存在flags里. 按说flags按值传递的, 如何保存irq状态呢? 因为spin_lock_irqsave不是函数而是宏. ^_^
spin_lock_irq如果可以确定没有其他地方禁止中断(因为对应的unlock函数会打开中断), 可以使用这个函数
spin_lock_bh在获取锁之前禁止软中断.
之所以需要引入irq/soft irq开关支持,是因为如果在线程内拥有锁, 这时候有中断进来, 而中断也要拥有锁才能工作, 就导致了死锁
void spin_unlock(spinlock_t *lock)
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
void spin_unlock_irq(spinlock_t *lock)
void spin_unlock_bh(spinlock_t *lock)
与加锁对应的个个版本的解锁
int spin_trylock(spinlock_t *lock)
int spin_trylock_bh(spinlock_t *lock)
非阻塞操作. 没有禁止中断的"try"版本
rwlock_t my_rwlock = RW_LOCK_UNLOCKED
rwlock_init(&my_rwlock)
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)
读写锁版本的spin lock
用其他方式避免使用锁
阻塞 I/O
运行在原子上下文时不能睡眠. 持有一个自旋锁, seqlock, RCU 锁或中断已关闭时不能睡眠. 但在持有一个旗标时睡眠是合法的
不能对醒后的系统状态做任何的假设, 并且必须检查来确保你在等待的条件已经满足
确保有其他进程会做唤醒动作
明确的非阻塞 I/O 由 filp->f_flags 中的 O_NONBLOCK/O_NDELAY 标志来指示. 只有 read, write, 和 open 文件操作受到非阻塞标志影响
下列情况下应该实现阻塞
DECLARE_WAIT_QUEUE_HEAD(name);
定义并初始化一个等待队列
init_waitqueue_head(wait_queue_head_t *name);
wait_event(queue, condition)
wait_event_interruptible(queue, condition)
wait_event_timeout(queue, condition, timeout)
wait_event_interruptible_timeout(queue, condition, timeout)
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);
6.2.5.1. 一个进程如何睡眠
放弃处理器是最后一步, 但是要首先做一件事: 你必须先检查你在睡眠的条件. 做这个检查失败会引入一个竞争条件; 如果在你忙于上面的这个过程并且有其他的线程刚刚试图唤醒你, 如果这个条件变为真会发生什么? 你可能错过唤醒并且睡眠超过你预想的时间. 因此, 在睡眠的代码下面, 典型地你会见到下面的代码:
if (!condition)
schedule();
通过在设置了进程状态后检查我们的条件, 我们涵盖了所有的可能的事件进展. 如果我们在等待的条件已经在设置进程状态之前到来, 我们在这个检查中注意到并且不真正地睡眠. 如果之后发生了唤醒, 进程被置为可运行的不管是否我们已真正进入睡眠.
如果在if判断之后,schedule之前,有其他进程试图唤醒当前进程, 那么当前进程就会被置为可运行的(但可能会到下次调度才会再次运行), 所以这个过程是安全的
while (time_before(jiffies, j1))
cpu_relax();
while (time_before(jiffies, j1)) {
schedule();
}