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负载。
原文地址:https://www.cnblogs.com/Dreaming-in-Gottingen/p/14675027.html