1:linux字符设备及udev

1.1字符设备

字符设备就是:一个一个字节来进行访问的,不能对字符设备进行随机读写。简单字符设备创建实例如下:

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/types.h>
  4. #include <linux/fs.h>
  5. #include <linux/init.h>
  6. #include <linux/delay.h>
  7. #include <asm/uaccess.h>
  8. #include <asm/irq.h>
  9. #include <asm/io.h>
  10. #include <linux/cdev.h>
  11. #include <linux/device.h>
  12. #define LED_MAJOR 241    /*预设的LED_MAJOR的主设备号*/
  13. static int led_major = LED_MAJOR;
  14. volatile unsigned long *gpbcon = NULL ;
  15. volatile unsigned long *gpbdat = NULL ;
  16. volatile unsigned long *gpbup = NULL ;
  17. struct cdev cdev; /*cdev结构体*/
  18. static int led_drv_open(struct inode *inode, struct file *file)
  19. {
  20. printk("first_drv_open\n");
  21. return 0;
  22. }
  23. static ssize_t led_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
  24. {
  25. return 0;
  26. }
  27. static struct file_operations led_drv_fops = {
  28. .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
  29. .open   =   led_drv_open,
  30. .write  =   led_drv_write,
  31. };
  32. /*初始化并注册cdev*/
  33. static void led_setup_cdev(void)
  34. {
  35. int err, devno = MKDEV(led_major, 0);//index 为从设备号
  36. cdev_init(&cdev, &led_drv_fops);
  37. cdev.owner = THIS_MODULE;
  38. cdev.ops = &led_drv_fops;
  39. err = cdev_add(&cdev, devno, 1);//devno 为第一个设备号,1为数量
  40. if (err)
  41. printk(KERN_NOTICE "Error %d adding", err);
  42. }
  43. static int led_drv_init(void)
  44. {
  45. int result;
  46. dev_t devno;
  47. devno=MKDEV(led_major,0);
  48. if(led_major)//静态申请设备号
  49. result=register_chrdev_region(devno,1,"led1_dev");
  50. else
  51. {
  52. result = alloc_chrdev_region(&devno,0,1,"led1_dev");//动态申请设备号
  53. led_major = MAJOR(devno);
  54. }
  55. if(result<0)
  56. {
  57. printk (KERN_WARNING "hello: can't get major number %d\n", led_major);
  58. return result;
  59. }
  60. led_setup_cdev();
  61. return 0;
  62. }
  63. static void led_drv_exit(void)
  64. {
  65. cdev_del(&cdev); // 注销cdev
  66. unregister_chrdev_region(MKDEV(led_major,0),1);//释放设备号
  67. }
  68. module_init(led_drv_init);
  69. module_exit(led_drv_exit);
  70. MODULE_LICENSE("GPL");

上面是一个字符设备的框架,没有实质性的东西,如:没有实现file_operations结构中的成员函数,同时led_drv_open(),led_drv_write()没有做任何东西。

现在将创建一个基本的字符设备过程总结如下:

C:初始化并关联file_operations结构体。  cdev_init(&cdev, &led_drv_fops);其中file_operations结构体中主要定义对字符设备的操作函数。

D:添加字符设备到内核。int cdev_add(struct cdev *p, dev_t dev, unsigned count),

E:移除字符设备及设备号。    cdev_del(&cdev); unregister_chrdev_region(MKDEV(led_major,0),1);完成于cdev_add()和register_chrdev_region()相反的工作

上面涉及到的API可以在函数linux/fs/char_dev.c中找到定义。

按照上面的方法可以创建一个主设备号为:241,次设备号为:0的一个字符设备。

可以讲上面的代码编译进内核:.

其方式如下:

将上面的代码保存到test.c中

在[root@centos /opt/FriendlyARM/linux-2.6.32.2/drivers]$ 目下下面建立文件件 mkdir suiyuan

之后在suiyuan中创建makefile,将上面的代码直接编译进内核,因此可以不需要kconfig文件

其makefile的内容为:

obj-$(CONFIG_SUIYUAN_HELLOWORLD)+= suiyuan_hello_module.o
#obj-$(CONFIG_STATIC_SUIYUAN_HELLOWORLD) += static_suiyuan_hello_module.o
obj-m+= static_suiyuan_hello_module.o
obj-y+= class_device_driver.o
obj-y+= leds.o
obj-y+= test.o
#obj-y+= ledsv2.o
#obj-y+= ledsv3.o

表示将obj-y表示将test编译进内核,在加载内的时候将该字符设备加载进内核。

同时在再修改/opt/FriendlyARM/linux-2.6.32.2/drivers目录下面的Makefile文件,在Makefile文件的末尾添加obj-y    += suiyuan/ 表示对suiyuan目录下面的所有文件进行编译。

有上面可以知道kconfig文件费必须,同时只有在需要将内核编译为模块的时候,才需要编写配置文件kconfig。

重新编译内核,将编译好的内核转化为uImage 格式。

对于上面创建的字符设备在使用的时候需要,创建设备文件。创建设备文件使用命令:mknod

mknod的使用方式如下:

定义:mknod - make block or character special files

语法:mknod /dev/ttyUSBn c Major Minor

1,n要等于次设备号Minor,且要小于主设备号Major.

2, c:面向字符设备(b:面向块设备,如:磁盘、软盘、磁带;其他设备基本都为字符设备).

在上面的代码中,我们的主从设备号为:241和0。故创建设备节点:mknod /dev/suiyuan c 241 0

之后可以[@mini2440 /dev]#cat test
led_drv_open
cat: read error: Invalid argument

如上面的结果,表示应用程序cat在访问设备文件的时候,调用了file_operations 数据结构中的open()函数。应用程序和底层驱动就此联系了起来。

1.2 udev是什么及其作用

udev文件系统是针对2.6内核,提供一个基于用户空间的动态设备节点管理和命名的使用程序,udev 以守护进程的形式运行,通过侦听内核发出来的 uevent 来动态的管理/dev目录下的设备文件。:udev是一种工具,它能够根据系统中的硬件设备的状态动态更新设备文件,包括设备文件的创建,删除等。设备文件通常放在/dev目录下。使用udev后,在/dev目录下就只包含系统中真正存在的设备。udev动态的根据根文件系统目录下面的/sys文件主动的创建设备文件,故在编写好设备驱动的时候,不需要再手动通过命令mknod来创建设备文件。udev涉及到/sys目录下面的文件,而/sys涉及到linux设备驱动模式。以后自己在总结这方面的东西。udev是一个用户程序(user-mode
daemon)。

现在就如何通过udev动态的创建/dev/目录下面的文件为例进行说明。

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/types.h>
  4. #include <linux/fs.h>
  5. #include <linux/init.h>
  6. #include <linux/delay.h>
  7. #include <asm/uaccess.h>
  8. #include <asm/irq.h>
  9. #include <asm/io.h>
  10. #include <linux/cdev.h>
  11. #include <linux/device.h>
  12. #include <asm/system.h>       /* cli(), *_flags */
  13. #include <asm/uaccess.h>  /* copy_*_user */
  14. #include <asm/io.h>
  15. #include <linux/ioport.h>
  16. #include <linux/device.h>      /* for sys mdev */
  17. #define LEDS_DEBUG
  18. #undef PDEBUG             /* undef it, just in case */
  19. #ifdef LEDS_DEBUG
  20. #  ifdef __KERNEL__
  21. /* This one if debugging is on, and kernel space */
  22. #    define PDEBUG(fmt, args...) printk( KERN_EMERG "leds: " fmt, ## args)
  23. #  else
  24. /* This one for user space */
  25. #    define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args)
  26. #  endif
  27. #else
  28. #  define PDEBUG(fmt, args...) /* not debugging: nothing */
  29. #endif
  30. #define LEDS_MAJOR 400
  31. #define LEDON 0
  32. #define LEDOFF 1
  33. #define BUZON 2
  34. #define BUZOFF 3
  35. #define GPBCON 0x56000010
  36. struct leds_dev {
  37. unsigned char value;    /* When LED lighted, its value bit is 1, otherwise 0 */
  38. struct cdev cdev;
  39. };
  40. static struct class *leds_class;
  41. static struct leds_dev *leds_devp;
  42. static int leds_major = LEDS_MAJOR;
  43. static void *virtaddr;
  44. static int leds_open(struct inode *inode, struct file *filp)
  45. {
  46. printk("file_operations:open\n");
  47. return 0;
  1. }
  2. static int leds_release(struct inode *inode, struct file *filp)
  3. {
  4. printk("file_operations:open\n");
  5. return 0;
  6. }
  7. static ssize_t leds_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
  8. {
  9. printk("file_operations:read\n");
  10. return 0;
  11. }
  12. static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
  13. {
  14. printk("file_operations:write\n");
  15. return 0;
  16. }
  17. static int leds_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
  18. {
  19. printk("file_operations:ioctl\n");
  20. return 0;
  21. }
  22. static struct file_operations leds_fops =
  23. {
  24. .owner   = THIS_MODULE,
  25. .read    = leds_read,
  26. .ioctl   = leds_ioctl,
  27. .open    = leds_open,
  28. .release = leds_release,
  29. .write   = led_write
  30. };
  31. static void leds_setup_cdev(struct leds_dev *dev, int index)
  32. {
  33. int err, devno = MKDEV(leds_major, index);
  34. cdev_init(&dev->cdev, &leds_fops);
  35. dev->cdev.owner = THIS_MODULE;
  36. dev->cdev.ops = &leds_fops;
  37. err = cdev_add(&dev->cdev, devno, 1);
  38. if (err)
  39. {
  40. printk(KERN_NOTICE "Error adding LEDS\n");
  41. }
  42. }
  43. static int leds_init(void)
  44. {
  45. int result;
  46. dev_t devno = MKDEV(leds_major, 0);
  47. PDEBUG("enter led_init\n");
  48. if (leds_major)
  49. {
  50. result = register_chrdev_region(devno, 1, "leds");
  51. }
  52. else
  53. {
  54. result = alloc_chrdev_region(&devno, 0, 10, "leds");
  55. leds_major = MAJOR(devno);
  56. }
  57. if (result < 0)
  58. {
  59. return result;
  60. }
  61. leds_devp = kmalloc(sizeof(struct leds_dev), GFP_KERNEL);
  62. memset(leds_devp, 0, sizeof(struct leds_dev));
  63. leds_setup_cdev(leds_devp, 0);
  64. leds_class = class_create(THIS_MODULE, "suiyuan_leds_class");
  65. if (IS_ERR(leds_class))
  66. {
  67. printk(KERN_WARNING "failed in creating class\n");
  68. }
  69. //  class_device_create(leds_class, NULL, devno, NULL, "suiyuan_leds");//这个函数在较早的额内核可以使用
  70. device_create(leds_class, NULL, devno, NULL, "suiyuan_leds"); //高版本内核可以使用我的是2.6.32.2-FriendlyARM
  71. printk("leds initialized\n");
  72. return 0;
  73. }
  74. void suiyuan_leds_cleanup(void)
  75. {
  76. cdev_del(&leds_devp->cdev);
  77. kfree(leds_devp);
  78. unregister_chrdev_region(MKDEV(leds_major, 0), 1);
  79. //class_device_destroy(leds_class, MKDEV(leds_major, 0));
  80. device_destroy(leds_class, MKDEV(leds_major, 0));
  81. class_destroy(leds_class);
  82. printk("leds driver unloaded\n");
  83. }
  84. module_init(leds_init);
  85. module_exit(suiyuan_leds_cleanup);
  86. MODULE_AUTHOR("suiyuan");
  87. MODULE_LICENSE("Dual BSD/GPL");

上面的代码建立了一个简单字符的设备,但是在最后使用函数class_create()和device_create()用来分别在/sys和/dev目录下面创建相应的文件夹及文件。

首先在:在/sys目录中

[@mini2440 /sys/dev/char]#ls
10:130  116:16  1:11    400:0  //是自己创建的设备文件

[@mini2440 /sys/class]#ls
bdi                 input               net                 sound
block               leds                rtc                 suiyuan_leds_class 是上面的函数class_create()所创建
class_suiyuan       mem                 scsi_device         tty
firmware            misc                scsi_disk           vc
graphics            mmc_host            scsi_generic        video4linux
i2c-dev             mtd                 scsi_host           vtconsole

在./dev目录下面:

crw-rw----    1 0        0         400,   0 Feb 16 20:11 suiyuan_leds 是device_create()函数所创建的设备文件有对应的主从设备号

在执行 cat

[@mini2440 /dev]#cat suiyuan_leds
file_operations:open
file_operations:read
file_operations:open

表示调用了file_operations中的驱动函数,open()和read().

现在在说说 udev.conf文件,现在创建/etc/udev.conf文件在其文件中添加

如下类容:suiyuan_leds 0:5 0666 =MyTestLed #MyTestLed为设备suiyuan_leds的别名。在加载文件系统的时候,将执行udev.conf中的内容。

之后重现加载rootfs 。此时的/dev/如下:

[@mini2440 /dev]#ls -al
total 4
drwxr-xr-x    3 0        0                0 Feb 17 08:34 .
drwxr-xr-x   16 0        0             4096 Feb 13 11:08 ..
crw-rw-rw-    1 0        5         400,   0 Feb 17 08:34 MyTestLed

同时也可以参考mini2440上面提供的udev.conf 文件中的内容。

2:misc设备

下面是一个misc设备的主要组成,结构相对明确。

  1. #include <linux/miscdevice.h>
  2. #include <linux/delay.h>
  3. #include <asm/irq.h>
  4. #include <linux/kernel.h>
  5. #include <linux/module.h>
  6. #include <linux/init.h>
  7. #include <linux/mm.h>
  8. #include <linux/fs.h>
  9. #include <linux/types.h>
  10. #include <linux/delay.h>
  11. #include <linux/moduleparam.h>
  12. #include <linux/slab.h>
  13. #include <linux/errno.h>
  14. #include <linux/ioctl.h>
  15. #include <linux/cdev.h>
  16. #include <linux/string.h>
  17. #include <linux/list.h>
  18. #include <linux/pci.h>
  19. #include <asm/uaccess.h>
  20. #include <asm/atomic.h>
  21. #include <asm/unistd.h>
  22. static int leds_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
  23. {
  24. printk ("misc  leds_ioctl\n");
  25. return 0;
  26. }
  27. static int leds_open(struct inode *inode, struct file *filp)
  28. {
  29. printk ("misc  in leds_open\n");
  30. return 0;
  31. }
  32. static int leds_release(struct inode *inode, struct file *filp)
  33. {
  34. printk ("misc leds_release\n");
  35. return 0;
  36. }
  37. static ssize_t leds_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
  38. {
  39. printk ("misc leds_read\n");
  40. return 1;
  41. }
  42. static struct file_operations leds_fops =
  43. {
  44. .owner   = THIS_MODULE,
  45. .read    = leds_read,
  46. .ioctl   = leds_ioctl,
  47. .open    = leds_open,
  48. .release = leds_release
  49. };
  50. static struct miscdevice misc = {
  51. .minor = MISC_DYNAMIC_MINOR,
  52. .name = "misc_leds", //此名称将显示在/dev目录下面
  53. .fops = &leds_fops,
  54. };
  55. static int __init dev_init(void)
  56. {
  57. int ret = misc_register(&misc);
  58. printk ("misc leds initialized\n");
  59. return ret;
  60. }
  61. static void __exit dev_exit(void)
  62. {
  63. misc_deregister(&misc);
  64. printk("misc leds unloaded\n");
  65. }
  66. module_init(dev_init);
  67. module_exit(dev_exit);
  68. MODULE_LICENSE("GPL");
  69. MODULE_AUTHOR("suiyuan");

将上面的代码保存为suiyuanMisc.c,并编译进内核。

[root@centos /opt/FriendlyARM/linux-2.6.32.2]$make
  CHK     include/linux/version.h
make[1]: `include/asm-arm/mach-types.h' is up to date.
  CHK     include/linux/utsrelease.h
  SYMLINK include/asm -> include/asm-arm
  CALL    scripts/checksyscalls.sh
  CHK     include/linux/compile.h
  CC      drivers/suiyuan/suiyuanMisc.o
  LD      drivers/suiyuan/built-in.o
  LD      drivers/built-in.o
  LD      vmlinux.o
  MODPOST vmlinux.o
WARNING: vmlinux: 'ledoff' exported twice. Previous export was in vmlinux
WARNING: vmlinux: 'ledon' exported twice. Previous export was in vmlinux
  GEN     .version
  CHK     include/linux/compile.h
  UPD     include/linux/compile.h
  CC      init/version.o
  LD      init/built-in.o
  LD      .tmp_vmlinux1
  KSYM    .tmp_kallsyms1.S
  AS      .tmp_kallsyms1.o
  LD      .tmp_vmlinux2
  KSYM    .tmp_kallsyms2.S
  AS      .tmp_kallsyms2.o
  LD      vmlinux
  SYSMAP  System.map
  SYSMAP  .tmp_System.map
  OBJCOPY arch/arm/boot/Image
  Kernel: arch/arm/boot/Image is ready
  GZIP    arch/arm/boot/compressed/piggy.gz
  AS      arch/arm/boot/compressed/piggy.o
  LD      arch/arm/boot/compressed/vmlinux
  OBJCOPY arch/arm/boot/zImage
  Kernel: arch/arm/boot/zImage is ready
  Building modules, stage 2.
  MODPOST 17 modules
WARNING: vmlinux: 'ledoff' exported twice. Previous export was in vmlinux
WARNING: vmlinux: 'ledon' exported twice. Previous export was in vmlinux

将zImage转化为uImage,并通过nfs下载到内容,并启动。可以在启动的时候看到:misc leds initialized 信息。

之后可以在mini2440的/dev/目录下面看到。

有如下设备:crw-rw----    1 0        0          10,  56 Feb 18 14:44 misc_leds_suiyuan  主设备号为:10,次设备号为:56

同时可以看看文件:/proc/misc,其文件中记载了系统中所加载misc设备。

53 network_throughput
 54 network_latency
 55 cpu_dma_latency
 56 misc_leds_suiyuan
 57 leds_03
130 watchdog
 58 camera
 59 adc
 60 pwm
 61 buttons
 62 leds
 63 backlight

misc总结如下:

misc字符设备,该类设备使用同一个主设备号10,misc字符设备使用的数据结构
struct miscdevice {
int minor;
const char *name;
struct file_operations *fops;
struct list_head list;
struct device *dev;
struct class_device *class;
char devfs_name[64];
};

杂项设备(misc device)
在 Linux 内核的include\linux\miscdevice.h文件,要把自己定义的misc
device从设备定义在这里。其实是因为这些字符设备不符合预先确定的字符设备范畴,所有这些设备采用主设备号10 ,一起归于misc
device,其实misc_register就是用主设备号10调用register_chrdev()的。也就是说,misc设备其实也就是特殊的字符设备。

misc_device是特殊的字符设备。注册驱动程序时采用misc_register函数注册,此函数中会自动创建设备节点,即设备文件。无需mknod指令创建设备文件。因为misc_register()会调用class_device_create()或者device_create()。

因此使用misc创建字符设备最简单方便。

linux之misc及使用misc创建字符设备的更多相关文章

  1. 自动创建字符设备,不需mknod

    自动创建设备文件 1.自动创建设备文件的流程 字符设备驱动模块 -->创建一个设备驱动class--->创建属于class的device--->调用mdev工具(自动完成)--> ...

  2. Linux驱动实践:你知道【字符设备驱动程序】的两种写法吗?

    作 者:道哥,10+年嵌入式开发老兵,专注于:C/C++.嵌入式.Linux. 关注下方公众号,回复[书籍],获取 Linux.嵌入式领域经典书籍:回复[PDF],获取所有原创文章( PDF 格式). ...

  3. 嵌入式Linux驱动学习之路(二十七)字符设备驱动的另一种写法

    之前讲的字符设备驱动程序,只要有一个主设备号,那么次设备号无论是什么都会和同一个 struct file_operations 结构体对应. 而本节课讲的是如何在设备号相同的情况下,让不同的次设备号对 ...

  4. 嵌入式Linux驱动学习之路(二十一)字符设备驱动程序总结和块设备驱动程序的引入

    字符设备驱动程序 应用程序是调用C库中的open read write等函数.而为了操作硬件,所以引入了驱动模块. 构建一个简单的驱动,有一下步骤. 1. 创建file_operations 2. 申 ...

  5. 跟着内核学框架-从misc子系统到3+2+1设备识别驱动框架

    misc子系统在Linux中是一个非常简单的子系统,但是其清晰的框架结构非常适合用来研究设备识别模型.本文从misc子系统的使用出发,通过了解其机制来总结一套的设备识别的驱动框架,即使用使用同一个驱动 ...

  6. Linux 设备驱动之字符设备

    参考转载博客:http://blog.chinaunix.net/uid-26833883-id-4369060.html https://www.cnblogs.com/xiaojiang1025/ ...

  7. Linux驱动开发2——字符设备驱动

    1.申请设备号 #include <linux/fs.h> int register_chrdev_region(dev_t first, unsigned int count, char ...

  8. Linux 字符设备驱动模型

    一.使用字符设备驱动程序 1. 编译/安装驱动 在Linux系统中,驱动程序通常采用内核模块的程序结构来进行编码.因此,编译/安装一个驱动程序,其实质就是编译/安装一个内核模块 2. 创建设备文件 通 ...

  9. Linux 简单字符设备驱动程序 (自顶向下)

    第零章:扯扯淡 特此总结一下写的一个简单字符设备驱动程序的过程,我要强调一下“自顶向下”这个介绍方法,因为我觉得这样更容易让没有接触过设备驱动程序的童鞋更容易理解,“自顶向下”最初从<计算机网络 ...

随机推荐

  1. python_day6学习笔记

    一.Logger模块 logging.basicConfig函数 可通过具体参数来更改logging模块默认行为,可用参数有 filename: 用指定的文件名创建FiledHandler(后边会具体 ...

  2. 属性名、变量名与 内部关键字 重名 加&

    procedure TForm4.btn3Click(Sender: TObject); var MyQj: TQJson; MyPrinter: TPrinter; begin MyQj := TQ ...

  3. AC自动机算法

    AC自动机简介:  首先简要介绍一下AC自动机:Aho-Corasick automation,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法之一.一个常见的例子就是给出n个单词,再给出一段 ...

  4. K8s的内部Pod之间都不通,搞了快两天

    试了不亚于二十种方法,绝望的时候,回到了家. 想手工安装,又遇到flannel在手工下,会更改docker启动项的不完善. cni,或许就是k8s的大方向吧. 最后,抱着试一试的态度,将flannel ...

  5. yum -y install 和yum install 的区别

    yum -y install 包名(支持*) :自动选择y,全自动 yum install 包名(支持*) :手动选择y or n yum remove 包名(不支持*) rpm -ivh 包名(支持 ...

  6. Linux下如何查看文档的内容

    查看文档内容的命令有:cat tac head nl tail more less odcat命令显示文档的全部内容,当文档较大的时候只显示最后的部分,所以cat命令适合查看内容较少的文档.可加选项- ...

  7. 五十三 网络编程 TCP/IP简介

    虽然大家现在对互联网很熟悉,但是计算机网络的出现比互联网要早很多. 计算机为了联网,就必须规定通信协议,早期的计算机网络,都是由各厂商自己规定一套协议,IBM.Apple和Microsoft都有各自的 ...

  8. CentOS 7.4 如何安装 MariaDB 10.3.9 Stable 数据库

    CentOS 7.4 如何安装 MariaDB 10.3.9 Stable 数据库 一.CentOS 7.4上卸载 Mariadb 数据库 1.查询所安装的MariaDB组件 [libin@VM_0_ ...

  9. 解决Mysql 服务无法启动 服务没有报告任何错误

    MySQL数据库在升级到5.7版本后,和之前的版本有些不一样,没有data文件夹,我们都知道MySQL数据库文件是保存在data文件夹中的,网上有人说把5.6版本的data文件夹拷贝一个,这种说法听听 ...

  10. Druid 架构

    本篇译自 Druid 项目白皮书部分内容( https://github.com/apache/incubator-druid/tree/master/publications/whitepaper/ ...