字符设备驱动

字符设备驱动程序是由一个cdev结构描述的,其定义为:
 struct cdev {
      struct kobject kobj;//内嵌的kobject
         struct module *owner;
         const struct file_operations *ops;
         struct list_head list;//与字符设备文件对应的索引节点链表的头,该链表用于收集相同字符设备驱动程序
         //所对应的设备文件的索引节点。
         dev_t dev;//初始主设备号和次设备号
         unsigned int count;//设备号的范围大小
 };
内核提供了cdev_alloc()函数,用于动态的分配cdev描述符,并初始化内嵌的kobject数据结构,因此引用计数器的值变为0
时会自动释放该描述符。
struct cdev *cdev_alloc(void)
 {
         struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
         if (p) {
                 INIT_LIST_HEAD(&p->list);
                 kobject_init(&p->kobj, &ktype_cdev_dynamic);
         }
         return p;
 }
在添加进系统之前则要调用cdev_init()函数来对cdev进行初始化。
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
        memset(cdev, 0, sizeof *cdev);
        INIT_LIST_HEAD(&cdev->list);
        kobject_init(&cdev->kobj, &ktype_cdev_default);
        cdev->ops = fops;
}   
cdev_add()函数向系统添加一个cdev字符设备。
 int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
     p->dev = dev;
     p->count = count;
  return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}
使用alloc_chrdev_region()和register_chrdev_region()函数为驱动分配任意范围内的设备号。
使用register_chrdev()函数分配一个固定的设备号范围.在这种情形下,设备驱动程序不必调用cdev_add()函数。
unregister_chrdev_region(dev_t from,unsigned count)
<b>2.常见函数</b>
copy_to_user(buf, &global_var, sizeof(int));
copy_from_user(&global_var, buf, sizeof(int));
可以通过filp->private_data传递私有数据。
3.驱动中的并发操作
(1)屏蔽中断
local_irq_disable() ;          //屏蔽中断
local_irq_enabale();     //打开中断
只能禁止和使能本CPU的中断,所以并不能解决SMP引发的竞争。
(2)原子操作
// 设置原子变量的值
void atomic_set(atomic_t *v, int i);  // 设置原子变量的值为i
atomic_t v = ATOMIC_INIT(0);  // 定义原子变量v,并初始化为0
// 获取原子变量的值
atomic_read(atomic_t *v);  // 返回原子变量的值
// 原子变量加/减
void atomic_add(int i, atomic_t *v);  // 原子变量加i
void atomic_sub(int i, atomic_t *v);  // 原子变量减i
// 原子变量自增/自减
void atomic_inc(atomic_t *v);  // 原子变量增加1
void atomic_dec(atomic_t *v);  // 原子变量减少1
 // 操作并测试:对原子变量进行自增、自减和减操作后(没有加)测试其是否为0,为0则返回true,否则返回false
int atomic_inc_and_test(atomic_t *v);
int atomic_dec_and_test(atomic_t *v);
int atomic_sub_and_test(int i, atomic_t *v);
// 操作并返回: 对原子变量进行加/减和自增/自减操作,并返回新的值
int atomic_add_return(int i, atomic_t *v);
int atomic_sub_return(int i, atomic_t *v);
int atomic_inc_return(atomic_t *v);
int atomic_dec_return(atomic_t *v);
  位原子操作:
// 设置位
void set_bit(nr, void *addr);  // 设置addr地址的第nr位,即将位写1
// 清除位
void clear_bit(nr, void *addr);  // 清除addr地址的第nr位,即将位写0
// 改变位
void change_bit(nr, void *addr);  // 对addr地址的第nr位取反
// 测试位
test_bit(nr, void *addr); // 返回addr地址的第nr位
// 测试并操作:等同于执行test_bit(nr, void *addr)后再执行xxx_bit(nr, void *addr)
int test_and_set_bit(nr, void *addr);
int test_and_clear_bit(nr, void *addr);
int test_and_change_bit(nr, void *addr);
(3)自旋锁(spin lock)——“在原地打转”
// 定义自旋锁
spinlock_t spin;
// 初始化自旋锁
spin_lock_init(lock);
// 获得自旋锁:若能立即获得锁,它获得锁并返回,否则,自旋,直到该锁持有者释放
spin_lock(lock);
// 尝试获得自旋锁:若能立即获得锁,它获得并返回真,否则立即返回假,不再自旋
spin_trylock(lock);
// 释放自旋锁: 与spin_lock(lock)和spin_trylock(lock)配对使用
spin_unlock(lock);
自旋锁可以保证临界区不受别的CPU和本CPU内的抢占进程打扰,但是得到锁的代码路径在执行临界区的时候还可能受到中断和底半部(BH)的影响。
为防止这种影响,需要用到自旋锁的衍生:
spin_lock_irq() = spin_lock() + local_irq_disable()
spin_unlock_irq() = spin_unlock() + local_irq_enable()
spin_lock_irqsave() = spin_lock() + local_irq_save()
spin_unlock_irqrestore() = spin_unlock() + local_irq_restore()
spin_lock_bh() = spin_lock() + local_bh_disable()
spin_unlock_bh() = spin_unlock() + local_bh_enable()
(4)信号量
当获取不到信号量时,进程不会原地打转而是进入休眠等待状态。
// 定义信号量
struct semaphore sem;
// 初始化信号量:
// 初始化信号量,并设置sem的值为val
void sema_init(struct semaphore *sem, int val);
// 初始化一个用于互斥的信号量,sem的值设置为1。等同于sema_init(struct semaphore *sem, 1)
void init_MUTEX(struct semaphore *sem);
// 等同于sema_init(struct semaphore *sem, 0)
void init_MUTEX_LOCKED(struct semaphore *sem);
// 下面两个宏是定义并初始化信号量的“快捷方式”:
DECLEAR_MUTEX(name)
DECLEAR_MUTEX_LOCKED(name)
//  获得信号量:
//  用于获得信号量,它会导致睡眠,不能在中断上下文使用
void down(struct semaphore *sem);
// 类似down(),因为down()而进入休眠的进程不能被信号打断,而因为down_interruptible()而进入休眠的进程能被信号打断,
// 信号也会导致该函数返回,此时返回值非0
void down_interruptible(struct semaphore *sem);
// 尝试获得信号量sem,若立即获得,它就获得该信号量并返回0,否则,返回非0.它不会导致调用者睡眠,可在中断上下文使用
int down_trylock(struct semaphore *sem);
//  使用down_interruptible()获取信号量时,对返回值一般会进行检查,若非0,通常立即返回-ERESTARTSYS
// 释放信号量
// 释放信号量sem, 唤醒等待者
void up(struct semaphore *sem);
(5)完成量(completion)提供了一种比信号量更好的同步机制,它用于一个执行单元等待另一个执行单元执行完某事。
completion相关操作:
// 定义完成量
struct completion my_completion;
// 初始化completion
init_completion(&my_completion);
// 定义和初始化快捷方式:
DECLEAR_COMPLETION(my_completion);
// 等待一个completion被唤醒
void wait_for_completion(struct completion *c);
// 唤醒完成量
void cmplete(struct completion *c);
void cmplete_all(struct completion *c);
3.阻塞和非阻塞
(1)阻塞
使用等待队列实现阻塞I/O。信号量在内核中就是依赖等待队列来实现的。
/定义初始化‘等待队列头’
Wait_queue_head_t  my_queue;
Init_wait_queue_head(&my_queue);
DECLARE_WAIT_QUEUE_HEAD (name);
//定义并初始化一个等待队列
DECLARE_WAITQUEUE(name, tsk);
//将等待队列添加到等待队列头Q指向的等待队列链表中
Void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
//将等待队列从等待队列头q指向的等待队列链表中移除
Void fastcall remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);
//等待事件,queue为等待队列头,timeout以jiffy为单位
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 *q);
Void wake_up_interruptible (wait_queu_head_t *q);
注意:wake_up可唤醒wait_event或者wait_event_interruptible,但是    wake_up_interruptible只能唤醒wait_event_interruptible。
//在等待队列上睡眠
Sleep_on(wait_queue_head_t *q);
Interruptible_sleep_on(wait_queue_head_t *q);
注意:sleep_on或者interruptible_sleep_on函数的作用就是将目前进程的状态设置成TASK_UNINTERRUPTIBLE或者INTERRUPTIBLE。并定义
一个等待队列,之后把它附属到等待队列头q,直到资源可获得,q引导的队列别唤醒或者接到信号。Sleep_on函数应该与wake_up成对使用,
interruptible_sleep_on应该与wake_up_interruptble 成对使用。
set_current_state()(设置当前进程的运行状态)和signal_pending();current代表当前进程。
(2)非阻塞使用select()轮询
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
struct fd_set为一个集合,存放的是文件描述符(filedescriptor),毫无疑问一个socket就是一个文件描述符。
fd_set操作,比如:
FD_ZERO(fd_set *); 清空集合
FD_SET(int, fd_set *); 将一个给定的文件描述符加入集合之中
FD_CLR(int, fd_set*); 将一个给定的文件描述符从集合中删除
检查集合中指定的文件描述符是否可以读写FD_ISSET(int ,fd_set* )。

struct timeval 是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒。 具体解释select的参数:
int maxfdp:是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!
fd_set* readfds:是指向fd_set结构的指针,监视这些文件描述符的读变化的,可以传入NULL值,表示不关心任何文件的读变化。
fd_set* writefds:是指向fd_set结构的指针,监视这些文件描述符的写变化的。可以传入NULL值,表示不关心任何文件的写变化。
fd_set * errorfds:同上面两个参数的意图,用来监视文件错误异常。
struct timeval* timeout:是select的超时时间,这个参数至关重要,它可以使select处于三种状态:
若将NULL以形参传入,就将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;
若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;
timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了。
select 返回值:
负值:select错误
正值:某些文件可读写或出错
0:等待超时,没有可读写或错误的文件

你可能感兴趣的:(字符设备驱动)