zoukankan      html  css  js  c++  java
  • kernel开发——semaphore、atomic、wait_queue_t

    最近工作业务方向由native framework层转换到kernel层,主要是维护编解码设备驱动。
    对driver这块的研究已经是五六年前的事了,后来就一直在中间件这块展开工作,并且五六年前还是手捧着LDD3业余学习这块,因此,目前亟需补一补这方面的基础知识。例如misc设备驱动怎么用了,多进程并发访问一个设备时,如何管理访问顺序了。

    主要涉及以下知识点:

    • misc设备驱动

    • 信号量、原子操作、等待队列用法

    • 应用层测试方法

    本着分享目的,已将测试代码上传至kernel-journey
    需要补充说明一点,driver测试基于kernel-4.14,app测试基于Android4.4平台。


    misc设备驱动

    提取骨骼框架如下:

    static int test_open(struct inode *inode, struct file *filp)
    {
    ... // trigger when open("/dev/api_test", O_RDWR)
    }
    static int test_release(struct inode *inode, struct file *filp)
    {
    ...// close(fd)
    }
    static long test_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
    {
    ...// ioctl(fd)
    }
    
    static const struct file_operations test_fops = {
        .owner = THIS_MODULE,
        .open = test_open,
        .release = test_release,
        .unlocked_ioctl = test_ioctl,
        .compat_ioctl = NULL,
    };
    
    static struct miscdevice test_dev = {
        .minor = TEST_MINOR,
        .name = "api_test",
        .fops = &test_fops,
    };
    
    static const struct of_device_id of_match_test_table[] = {
        { .compatible = "xcom,mytest", .data = NULL },
        { },
    };
    
    static int test_probe(struct platform_device *pdev)
    {
    ... // trigger when insmod
    }
    static int test_remove(struct platform_device *pdev)
    {
    ... // trigger when rmmod
    }
    static struct platform_driver test_driver = {
        .probe = test_probe,
        .remove = test_remove,
    
        .driver = {
            .owner = THIS_MODULE,
            .name = "api_test",
            .of_match_table = of_match_test_table,
        },
    };
    
    module_platform_driver(test_driver);
    MODULE_DESCRIPTION("KERNEL Common API Test");
    MODULE_LICENSE("GPL");
    

    原子、信号量、等待队列

    • atomic_t

    定义:这儿
    典型使用场景:多进程中共享资源的计数加减。例如,硬件设备往往只有一个,usr space层可能同时存在多个进程在访问该设备,则用此来保存client数。
    常用API:
    atomic_inc(&g_instance_cnt);
    atomic_dec(&g_instance_cnt);
    usr_val = atomic_read(&g_instance_cnt);
    ret = atomic_inc_return(&g_instance_cnt); //自增,并返回+1后的值

    • struct semaphore

    定义:这儿
    典型使用场景:用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。即资源有明确访问数量限制的场景,常用于限流。
    可以把它简单的理解成我们停车场入口立着的那个显示屏,每有一辆车进入停车场显示屏就会显示剩余车位减1,每有一辆车从停车场出去,
    显示屏上显示的剩余车辆就会加1,当显示屏上的剩余车位为0时,停车场入口的栏杆就不会再打开,车辆就无法进入停车场了,直到有一辆车从停车场出去为止。
    常用API:
    sema_init(&g_test_dev.mutex, 1); //资源数初始化为1
    up(&g_test_dev.mutex); //使用完了,释放,使资源数+1
    down(&g_test_dev.mutex); //block方式获取资源,如果拿不到,则app会被一直block,即使ctrl+c也不能中断等待。不推荐使用,替换用down_interruptible或down_killable
    down_timeout(&g_test_dev.mutex, msecs_to_jiffies(usr_val)); //设定超时时间来获得资源

    • wait_queue_t

    定义:这儿
    典型使用场景:通过睡眠的等待,就是当设备不可用时,由底层驱动来检测,检查,识别设备可用不可用,如果不可用,底层设备驱动就让应用程序进入休眠状态(结果让当前进程的CPU资
    源撤下来给别的任务去使用),并且底层驱动能够检查设备可用不可用,如果一旦检查到设备数据可用,再次唤醒休眠的进程(休眠的进程一旦被唤醒,就会获取CPU的资源),
    然后去读取数据即可。 例如,对于视频编码器来说,编码一帧需要几十毫秒,当启动了编码器进行编码时,可将该进程加入到等待队列中,直到被唤醒(超时或者编码完成后发出的中断)。
    常用API:
    init_waitqueue_head(&client->wait_queue);
    ret = wait_event_interruptible_timeout(client->wait_queue, 0, msecs_to_jiffies(DEV_TIMEOUT_MS));
    其他说明:本来想用定时器中断来模拟硬件的这个行为,无奈定时器中断还不熟悉,没跑起来,后面有空了再补充完整吧。

    应用层测试方法

    驱动完成了,如何测试驱动是否OK呢?参考下面这块代码(完整版在这儿):

    int main(void)
    {
        ALOGW("-------------ApiTester begin--------------");
    
        const char *dev = "/dev/api_test";
        int fd = open(dev, O_RDWR);
        ALOGD("open(%s) with ret_fd = %d, strerror=(%s)", dev, fd, strerror(errno));
        if (fd < 0) {
            ALOGE("open failed! forget to insmod api_test.ko?");
            return -1;
        }
    
        int val = 0;
        int ret = 0;
    
        ALOGD("-----------------IOCTL_GET/SET_VAL-----------------------");
        ALOGD("after IOCTL_GET_VAL, val = %d", val);
        ioctl(fd, TEST_GET_VAL, &val);          //先拿驱动中的值
        ALOGD("after IOCTL_GET_VAL, val = %d", val);
        ALOGD("wait for key in val and SET to kernel...");
        scanf("%d", &val);                      //再等待usr输入
        ioctl(fd, TEST_SET_VAL, &val);          //再设置给driver
        val = 0;
        ioctl(fd, TEST_GET_VAL, &val);          //最后再从driver中拿
        ALOGD("then GET from kernel, val=%d", val);
    
        ALOGD("-----------------IOCTL_SEMA_DOWN/UP-----------------------");
        while (1) {
            ALOGD("looping. key in up(1), down(2) or quit(others)...");
            scanf("%d", &val);
            // 1-up semaphore
            if (val == 1) {
                ioctl(fd, TEST_SEMA_UP, NULL);
            // 2-down semaphore. 又细分为几种down的方式。
            } else if (val == 2) {
                ALOGD("key in down_type(1-down; 2-interruptible; 3-killable; 4-trylock; others-TimeoutMs)...");
                ret = scanf("%d", &val);
                if ((ret!=1) || (val<0)) {
                    ALOGW("error input? for ret=%d, val=%d.", ret, val);
                    break;
                }
                ALOGD("begin to down_type(%d)!", val);
                ret = ioctl(fd, TEST_SEMA_DOWN, &val);
                ALOGD("finish down! DOWN_ret=%d", ret);
                ret = 0;
            } else {
                ALOGW("usr want to quit semaphore test! val=%d", val);
                break;
            }
        }
    
        ALOGD("-----------------IOCTL_INSTANCE_CNT-----------------------");
        ret = ioctl(fd, TEST_INSTANCE_CNT, &val);  //测试atomic
        ALOGD("current dev instance cnt=%d, ret=%d", val, ret);
    
        ALOGD("-----------------IOCTL_WAIT_EVENT-----------------------");
        ret = ioctl(fd, TEST_WAIT_EVENT, &val);    //测试wait_queue,内部用了timeout 2s的方式,等定时器中断会用了后,再增加这用方式的
        ALOGD("dev's task complete=%d, ret=%d", val, ret);
    
        close(fd);      //会调用到release()
    
        ALOGW("-------------ApiTester end--------------");
    
        return 0;
    }
    

    其他补充

    • 编译问题

      分为两部分编译,app+driver。在应用层编译没什么好说的,但编译成ko文件需要特别说明一下。
      没看到谷歌提供的编译kernel的命令,可能需要各芯片原厂定制吧。但万变不离其宗,所谓编译,无非用交叉编译器将源码编译成ko文件,需要写好Makefile、Kconfig,再调用(指定arch、cross_compiler)命令。
      本来刚开始尝试在kernel目录下编译,但没成功,参考了别人在vendor目录下放的一个,就这样就可以编译出ko文件了。
    • insmod后无probe()内输出log

      刚开始没在dts文件中指定设备节点,导致没输出,即只进行了module_init(),而没执行probe()。貌似自从有了dts这个东西后,所有设备必须在该文件中进行声明,才能在/dev/目录下看到该设备。
    • kill进程后无release()调用

      OS管理了进程所打开的所有fd,当退出时,会清理掉全部fd,即使用户没有主动调用close(),系统会帮你做这个事情。出现如上描述现象,原始猜测是:有些log会被过滤掉,即使log级别为最高等级,但一定会调用到release()。我曾尝试在release()函数内故意访问非法地址,结果系统每次都挂掉。
    • open(dev)/close(dev)

      可能有多个app进行了open()/close()操作。driver需要处理好并发访问问题。
    • 有时候内核log不输出或延迟输出

      猜测是内核设置的机制,冗余(完全重复的行)log可能被剔除来减轻cpu负载。
  • 相关阅读:
    linux less-分屏上下翻页浏览文件内容
    linux tail-在屏幕上显示指定文件的末尾若干行
    linux cut-连接文件并打印到标准输出设备上
    linux od-输出文件的八进制、十六进制等格式编码的字节
    linux hexdump-显示文件十六进制格式
    linux whereis-查找二进制程序、代码等相关文件路径
    linux find-在指定目录下查找文件
    linux which-查找并显示给定命令的绝对路径
    linux diff3-比较3个文件不同的地方
    Datetimepicker实现秒钟选择下拉框
  • 原文地址:https://www.cnblogs.com/Dreaming-in-Gottingen/p/14675027.html
Copyright © 2011-2022 走看看