zoukankan      html  css  js  c++  java
  • 驱动


    机制而非策略

    一、Linux设备驱动概述
    1.设备驱动分类
    1.字符设备(open,read,write,close,seek,ioctl,fcntl,mmap,select,aio_read),比如adc,uart,rtc,
    2.块设备(open,...,mount,umount)比如 nandflash(uboot擦除必须以块为单位)不可能一个一个字节的删除,存储设备
    3.网络接口设备(ifconfig,socket,bind,listen...)
    2.设备驱动作用
    应用层
    C程序..
    内核
    VFS
    Driver
    硬件
    3.设备文件
    /dev
    4.设备号
    major,minor
    5.地址空间
    MMU--->虚拟内存
    内核空间(1G)所有进程共享
    用户空间(3G)每个进程独立(0-3G)

    二、内核模块
    1.模块编写
    static int __init hello_init(void)
    {
    //...
    return 0;
    }
    static void __exit hello_exit(void)
    {
    //...
    }
    module_init(hello_init);
    module_exit(hello_exit);
    2.操作相关指令
    insmod
    rmmod(后边加不加ko.都行,正常情况下,不加 ***注意此处的rmmod 和insmod 用的是**_driver,而不是什么设备名)
    lsmod
    modinfo
    depmod
    modprobe
    3.参数
    支持的类型:byte short ushort int uint long ulong charp bool invbool + 数组
    module_param(name,type,perm);
    4.导出符号
    EXPORT_SYMBOL();
    EXPORT_SYMBOL_GPL();
    5.说明 modinfo
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("");
    MODULE_DESCRIPTION("");
    MODULE_VERSION("");
    MODULE_SUPPORTED_DEVICE("");
    MODULE_ALIAS("");
    6.相关几个文件
    /proc/modules ==> lsmod
    用命令cat /proc/modules | grep led_driver
    结果 led_driver 2284 0 - Live 0xf813d000
    用命令 lsmod | grep led
    结果 led_driver 2284 0
    /proc/kallsyms ==> 内核导出符号表
    /sys/module ==> 内核模块(我们添加以后会放到这个模块下)
    用命令 ls -l /dev/ | grep led
    结果 crw-rw---- 1 root root 250, 0 2014-09-13 09:48 myleds
    如果调用register_chrdev_region这个函数,在cat /proc/devices 下就有这个设备
    lsmod | head
    三、搭建环境
    1.板子跑起来
    uboot + kernel + filesystem(rootfs.jffs2/rootfs_qt.jffs2)
    2.准备和板子匹配的Linux内核源码树
    linux-2.6.32.2
    3.搭建tftp和nfs服务器
    1.安装tftp和nfs
    1.sudo apt-get install tftp
    2.sudo apt-get install nfs //这里是有网的时候用的。
    //====================================================nfs
    1.启动服务:
    service nfs-kernel-server restart
    2.设置共享目录
    vi /etc/exports
    /home/.../ *(rw,sync,no_root_squash)
    3.本机测试:
    mount -t nfs -o nolock -o tcp 127.0.0.1:/home/... /mnt
    ls /mnt
    umount /mnt //卸载挂载
    用完后直接卸载,要不你的WINDOS与虚拟机的共享桌面不能用,即使卸载也不影响你的远端测试。
    4.远端测试(必须执行上面的卸载,才能执行下面的步骤)
    mount -t nfs -o nolock -o tcp serverIp:/home/... /mnt
    ls /mnt
    umount /mnt
    //===================================================tftp
    tftp -gr hello.ko serverIp
    静态IP
    vim /etc/network/interfaces
    加入4行 auto eth5
    auto lo
    inface lo inet loopback
    auto eth5
    iface eth5 inet static
    address 192.168.12.95
    netmask 255.255.255.0
    重启 sudo /etc/init.d/networking restart
    板子上静态ip设置。
    vi /etc/net.conf
    IPADDR=192.168.7.116
    NETMASK=255.255.255.0
    GATEWAY=192.168.7.115
    MAC=10:23:45:67:89:ab
    注意不同的文件系统可以对应的修改的地方不同。


    错误分析
    问题1 mount.nfs: DNS resolution failed for sh1slcs001: Name or service not known
    答: IP问题
    问题2 有人说mount:RPC:Remote system error - Connection refused是因为server的portmap没开,但是我确实开了,iptables都已经被我禁掉了

    四、在开发板上运行hello.ko

    字符设备驱动
    一、设备编号
    1.设备号的内部表示
    dev_t
    MKDEV(int major,int minor);
    MAJOR(dev_t devno);
    MINOR(dev_t devno);
    2.设备号的分配和释放
    静态分配:
    int register_chrdev_region(dev_t first, unsigned int count, char *name);
    动态分配:
    int alloc_chrdev_region(dev_t *first,int firstminor,unsigned int count, char *name);
    释放:
    unregister_chrdev_region(dev_t devno,unsigned int count);
    二、重要数据结构
    struct file_operations
    {
    struct module *owner; //THIS_MODULE
    int (*open)(struct inode *,struct file *);
    int (*release)(struct inode *,struct file *);
    ssize_t (*read)(struct file *,char __user *buf,size_t,loff_t *);
    ssize_t (*write)(struct file *,const char __user *buf,size_t,loff_t *);
    loff_t (*llseek)(struct file *,loff_t,int);
    int (*ioctl)(struct inode *,struct file *,unsigned int,unsigned long);
    ...
    };
    struct file
    {
    unsigned int f_flags; /*文件的打开标志,O_RDONLY,O_NONBLOCK*/
    mode_t f_mode; /*文件的读写模式 FMODE_READ,FMODE_WRITE*/
    atomic_t f_count; /*文件的引用计数*/
    loff_t f_pos; /*当前读写位置*/
    void *private_data; /*私有数据*/
    //私有数据存放自己的数据
    struct file_operations *f_op; /*文件的操作函数*/
    ...
    };
    //每一次的open都会创建一个struct file的结构体。
    struct inode
    {
    //...文件的属性字段 i_mode,i_uid,i_gid,...
    dev_t i_rdev; /*如果是设备文件,代表设备号*/
    struct cdev *i_cdev; /*指向当前设备cdev的指针*/
    };

    三、字符设备
    1.Linux内核使用struct cdev代表一个字符设备
    struct cdev
    {
    struct module *owner;
    struct file_operations *ops;
    ...
    };
    2.将cdev通知内核(将cdev与设备号关联,并且添加到内核驱动列表)
    int cdev_add(struct cdev *, dev_t, unsigned);
    3.将cdev从内核删除
    void cdev_del(struct cdev *);


    与硬件通讯
    一、IO端口与IO内存
    1.X86
    独立编址 IO端口
    2.ARM、Mips、PowerPC
    统一编址 IO内存
    二、IO内存的操作
    1.申请
    request_mem_region();
    2.映射
    ioremap();
    3.使用
    ioread8(); ioread8_rep();
    ioread16(); ioread16_rep();
    ioread32(); ioread32_rep();
    iowrite8(); iowrite8_rep();
    iowrite16(); iowrite16_rep();
    iowrite32(); iowrite32_rep();
    4.解除映射
    iounmap();
    5.释放
    release_mem_region();

    任务1:修改led_driver.c
    任务2:beep_driver.c
    任务3:serial0_driver.c

    内核中的并发与竟态 && 阻塞

    一、中断屏蔽 ( 进程器调度的前提是中端,定时器中端)
    local_irq_disable(); //禁用所有中断
    local_irq_enable(); //使能所有中断

    local_irq_save(flags); //禁用所有中断并且保存中断状态
    local_irq_restore(flags); //使能中断并且恢复状态为flags

    local_bh_disable(); //禁用中断底半部
    local_bh_enable(); //使能中断底半部

    二、原子操作(不可打断的操作)
    1.整型原子操作
    1.初始化
    atomic_t v = ATOMIC_INIT(0);
    2.读写
    atomic_set(atomic_t *v,int val);
    int atomic_read(atomic_t *v);
    3.操作
    void atomic_add(int val,atomic_t *v); //v += val
    void atomic_sub(int val,atomic_t *v); //v -= val
    void atomic_inc(atomic_t *v); //v++
    void atomic_dec(atomic_t *v); //v--

    int atomic_inc_and_test(atomic_t *v); //if(++v==0)
    int atomic_dec_and_test(atomic_t *v); //if(--v==0)
    int atomic_sub_and_test(int val,atomic_t *v); //if((v-=val)==0)

    int atomic_add_return(int val,atomic_t *v); //v += val
    int atomic_sub_return(int val,atomic_t *v); //v -= val
    int atomic_inc_return(atomic_t *v); //v++
    int atomic_dec_return(atomic_t *v); //v--
    2.位原子操作
    void set_bit(nr,void *addr); //*addr |= 1<<nr
    void clear_bit(nr,void *addr); //*addr *= ~(1<<nr)
    void change_bit(nr,void *addr); //*addr ^= 1<<nr
    test_bit(nr,void *addr); //if((*addr >> nr) & 0x1)

    int test_and_set_bit(nr,void *addr); //先执行test,后执行set_bit
    int test_and_clear_bit(nr,void *addr);
    int test_and_change_bit(nr,void *addr);

    三、自旋锁机制
    1.定义自旋锁
    spinlock_t spinlock;
    2.初始化
    spin_lock_init(&spinlock);
    3.加锁(获取锁)
    spin_lock(&lock);
    spin_trylock(&lock);
    4.解锁(释放锁)
    spin_unlock(&lock);
    ========================
    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();

    5.何为自旋锁
    1.自旋锁的核心是一个原子变量(内部仅读循环)
    2.自旋锁加锁过程 相当于 “测试并设置”(test-and-set,原子操作)该原子变量
    空闲:则可以获取锁
    否则:程序将在一个小的循环内重复该“测试并设置”操作
    6.自旋锁使用注意事项:
    1.自选锁实际上是一个忙等待,适用于“占用锁时间极短的情况”
    2.自旋锁可能导致死锁
    3.自旋锁锁定期间,不允许出现可能引起进程调度的行为,否则可能会导致死锁:
    1.中断
    2.一些可能导致阻塞/睡眠的函数:copy_form_user,copy_to_user,kmalloc,sleep系列函数

    读写自旋锁:
    允许同时加多个读锁(读写互斥,写写互斥,读读允许)
    1.定义读写锁
    rwlock_t my_rwlock;
    2.初始化读写锁
    rwlock_init(&my_rwlock);
    3.加锁
    void read_lock(rwlock_t *rwlock);
    void read_lock_irq(rwlock_t *rwlock);
    void read_lock_irqsave(rwlock_t *rwlock,int flags);
    void read_lock_bh(rwlock_t *lock);

    void write_lock(rwlock_t *rwlock);
    void write_lock_irq(rwlock_t *rwlock);
    void write_lock_irqsave(rwlock_t *rwlock,int flags);
    void write_lock_bh(rwlock_t *lock);
    4.解锁
    void read_unlock(rwlock_t *rwlock);
    void read_unlock_irq(rwlock_t *rwlock);
    void read_unlock_irqsave(rwlock_t *rwlock,int flags);
    void read_unlock_bh(rwlock_t *lock);

    void write_unlock(rwlock_t *rwlock);
    void write_unlock_irq(rwlock_t *rwlock);
    void write_unlock_irqsave(rwlock_t *rwlock,int flags);
    void write_unlock_bh(rwlock_t *lock);
    顺序自旋锁:
    写写仍互斥,读操作时,写操作完成了,必须重新写。



    四、信号量
    1.定义信号量:
    struct semaphore sema;

    2.信号量初始化:
    void sema_init(struct semaphore *sema,int val);
    init_MUTEX(struct semaphore *sema); //初始化为1
    init_MUTEX_LOCKED(struct semaphore *sema); //初始化为0
    3.P函数 +1
    void down(struct semaphore *sema);
    int down_interruptible(struct semaphore *sema);
    ///// int down_interruptible(struct semaphore *sem)
    这个函数的功能就是获得信号量,如果得不到信号量就睡眠,此时没有信号打断,那么进入睡眠。但是在睡眠过程中可能被信号打断,打断之后返回-EINTR,主要用来进程间的互斥同步。
    int down_trylock(struct semaphore *sema);
    4.V函数 -1
    void up(struct semaphore *sema);
    信号量值不能为负
    4.1 信号量实现互斥
    init_MUTEX(sema);
    ssize_t xxx_read(...)
    {
    down(&sema);
    //...
    up(&sema);
    }
    ssize_t xxx_write(...)
    {
    down(&sema);
    //...
    up(&sema);
    }
    4.2 信号量实现同步
    init_MUTEX_LOCKED(sema);

    进程A:
    dosth1();
    down(&sema);
    dosth2();
    进程B:
    dosth3();
    up(&sema);

    五、完成量(Completions)
    5.1 完成量实现同步
    1.定义完成量:
    struct competion comp;
    2.初始化
    init_completion(&comp);
    3.等待完成
    wait_for_completion(&comp);
    4.通知完成
    complete(&comp);
    complete_all(&comp);

    进程A:
    dosth1();
    wait_for_completion(&comp);
    dosth2();
    进程B:
    dosth3();
    complete(&comp);/complete_all(&comp);


    05 进程睡眠
    一、基本API
    1.定义一个等待队列头:
    wait_queue_head_t myqueue;
    2.初始化等待队列头:
    init_waitqueue_head(&myqueue);
    DECLARE_WAIT_QUEUE_HEAD(name);
    3.睡眠
    wait_event(myqueue, condition)
    wait_event_interruptible(myqueue, condition)//响应任何信号,但是等待需要一个条件,这个条件
    wait_event_timeout(myqueue, condition, timeout)
    wait_event_interruptible_timeout(myqueue, condition, timeout)
    4.唤醒
    void wake_up(wait_queue_head_t *queue);
    void wake_up_interruptible(wait_queue_head_t *queue);

    二、相关函数
    1.定义等待队列
    wait_queue_t queue;
    init_wait(&my_wait);
    DEFINE_WAIT(my_wait);
    2.添加等待队列到等到队列头
    __add_wait_queue(q, wait);
    3.设置进程状态
    void set_current_state(int new_state);
    TASK_RUNNING 运行态/就绪态
    TASK_INTERRUPTIBLE 可打断休眠(信号)
    TASK_UNINTERRUPTIBLE 不可打断休眠
    current->state = TASK_INTERRUPTIBLE;
    4.让出CPU
    schedule();

    prepare_to_wait()//完成上面2,3步操作
    finish_wait() //取消上面2,3步操作
    1.设置进程状态为TASK_RUNNING
    set_current_state(TASK_RUNNING);
    2.将等待队列从等待队列头移除
    remove_wait_queue(q,wait);




    Linux 五种IO模型
    一、seek与ioctl的实现
    1.seek
    2.ioctl
    1.命令的组成:
    type 魔数,每个设备有唯一的魔数 8bit
    number 命令的序号
    direction 命令的方向
    size 传递参数的字节数
    2.如何定义命令
    _IO(type,nr)
    _IOR(type, nr, datatype)
    _IOW(type, nr, datatype)
    _IOWR(type,nr,datatype)
    3.如何解析命令
    _IOC_DIR(cmd)
    _IOC_TYPE(cmd)
    _IOC_NR(cmd)
    _IOC_SIZE(cmd)

    二、五种IO模型概述
    1.阻塞IO
    2.非阻塞IO(O_NONBLOCK)
    3.IO多路复用(select,O_NONBLOCK)
    4.信号驱动IO(SIGIO)
    5.异步IO(aio_read,aio_write,...)
    三、五种IO模型在驱动中的实现
    1.阻塞IO的实现
    1.当条件不满足就睡眠
    (wait_queue_head_t,wait_event_interruptible,wake_up_interruptible)
    2.非阻塞IO的实现
    1.当条件不满足
    1.判断filp->f_flags 是否有O_NONBLOCK标志,如果有则返回-EAGAIN
    3.IO多路复用的实现
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    poll函数实现两步骤操作:
    1.将当前设备中所有可能引起阻塞的 等待队列头(wait_queue_head_t)添加到poll_table
    void poll_wait (struct file *, wait_queue_head_t *, poll_table *);
    2.返回一个表示当前是否可读写的状态掩码
    POLLIN
    如果设备可被不阻塞地读, 这个位必须设置.
    POLLRDNORM
    这个位必须设置, 如果"正常"数据可用来读. 一个可读的设备返回( POLLIN|POLLRDNORM ).
    POLLRDBAND
    这个位指示带外数据可用来从设备中读取. 当前只用在 Linux 内核的一个地方( DECnet 代码 )并且通常对设备驱动不可用.
    POLLPRI
    高优先级数据(带外)可不阻塞地读取. 这个位使 select 报告在文件上遇到一个异常情况, 因为 selct 报告带外数据作为一个异常情况.
    POLLHUP
    当读这个设备的进程见到文件尾, 驱动必须设置 POLLUP(hang-up). 一个调用 select 的进程被告知设备是可读的, 如同 selcet 功能所规定的.
    POLLERR
    一个错误情况已在设备上发生. 当调用 poll, 设备被报告位可读可写, 因为读写都返回一个错误码而不阻塞.
    POLLOUT
    这个位在返回值中设置, 如果设备可被写入而不阻塞.
    POLLWRNORM
    这个位和 POLLOUT 有相同的含义, 并且有时它确实是相同的数. 一个可写的设备返回( POLLOUT|POLLWRNORM).
    POLLWRBAND
    如同 POLLRDBAND , 这个位意思是带有零优先级的数据可写入设备. 只有 poll 的数据报实现使用这个位, 因为一个数据报看传送带外数据.
    4.信号驱动IO的实现

    5.异步IO

    ==============================================
    07 Linux内核中断
    arm的中断分为5部,但是内核的中断分为2步,在操作系统下的编程。
    一、申请中断
    request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
    const char *name, void *dev);
    irq:中断编号 mach/irqs.h
    IRQ_EINT0
    IRQ_TIMER0
    handler: 中断处理函数
    typedef irqreturn_t (*irq_handler_t)(int, void *);
    irqreturn_t的取值:
    IRQ_NONE //没有处理
    IRQ_HANDLED //已经处理
    IRQ_WAKE_THREAD //线程唤醒处理方式
    flags: 中断触发方式 && 处理方式
    IRQF_TRIGGER_NONE
    IRQF_TRIGGER_RISING
    IRQF_TRIGGER_FALLING
    IRQF_TRIGGER_HIGH
    IRQF_TRIGGER_LOW
    ------------------
    IRQF_DISABLED 快速中断,中断处理期间,屏蔽其他所有中断
    IRQF_SHARED 中断共享
    一旦进入中断处理函数,flag就废了。
    name: 设备名 cat /proc/interrupts
    dev: 中断共享时使用,会作为参数被传递给中断处理函数

    二、释放中断
    void free_irq(unsigned int, void *);

    三、中断使能/禁用
    void disable_irq(int irqno); //禁用中断,等待已经发生的中断处理完毕
    void disable_irq_nosync(int irqno); //禁用中断,立刻返回
    void enable_irq(int irqno); //使能中断

    void local_irq_disable(); //禁用所有中断
    #define local_irq_save(flags); //禁用所有中断,并且保存中断状态到flags
    #define local_irq_restore(flags); //使能所有中断,并且状态设置为flags
    void local_irq_enable(); //使能所有中断

    四、中断顶半部和底半部
    1.进程上下文 && 中断上下文
    进程上下文允许调度
    中断上下文不允许调度
    2.中断处理程序处于中断上下文,不允许调度
    1.为了追求更高的系统吞吐率(及时响应能力),要求中断处理程序必须尽可能短
    2.通常情况下,中断要进行较大量的耗时处理
    3.基于1,2的矛盾,产生了中断处理架构:顶半部 + 底半部
    3.底半部机制:
    1.tasklet
    void mytasklet_func(unsigned long);
    DECLARE_TASKLET(name, mytasklet_func, data)
    tasklet_schedule(name);

    注意:tasklet机制仍然处于软中断上下文,而不是进程上下文,所有tasklet中仍然不能睡眠
    2.workqueue
    struct work_struct my_wq;
    void my_wq_func(unsigned long );
    INIT_WORK(&my_wq,my_wq_func,_data)
    schedule_work(&my_wq);

    注意:workqueue处于进程上下文,允许睡眠

    五、中断共享
    1.如何设置中断共享
    IRQF_SHARED
    2.中断共享的意义
    允许多个设备注册同一个中断号/中断线
    3.中断共享的问题
    多个设备给同一个中断号注册了多个中断处理函数,多个中断处理函数以链表的形式存在,
    当该中断产生时,会依次调用链表中的中断处理函数,直到某个函数返回 IRQ_HANDLED

    4.如何正确调用设备的中断处理函数
    typedef irqreturn_t (*irq_handler_t)(int, void *dev_id);
    request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
    const char *name, void *dev_id);

    在没有中断共享的情况下:
    dev_id ---> NULL
    如果设置了中断共享,dev_id通常用于区分每个设备
    dev_id ---> 当前设备结构指针

  • 相关阅读:
    matlab中用来批量读取的dir函数
    cat 函数应用
    线性移不变系统
    为什么低频信息描述了图像在光滑部位的整体灰度信息,而高频部分则反映了图像在边缘、噪声等细节方面的表现?
    红灯检测宇视科技专利分析与总结2
    红灯检测宇视科技专利分析与总结1
    matlab中冒号的用法
    第一篇博文,大橙子的博客生涯要开始啦
    Spring Boot和Shiro整合
    Spring Boot + Redis使用短信平台发送验证码(腾讯云短信平台)
  • 原文地址:https://www.cnblogs.com/coding4/p/5894637.html
Copyright © 2011-2022 走看看