抖音技术能力大揭密!钜惠大礼、深度体验,尽在火山引擎增长沙龙,就等你来! 立即报名>> 了解详情
写点什么

编写 Linux 内核模块——第一部分:前言

2015 年 10 月 26 日

【编者的话】Linux 内核模块作为 Linux 内核的扩展手段,可以在运行时动态加载和卸载。它是设备和用户应用程序之间的桥梁,可以通过标准系统调用,为应用程序屏蔽设备细节。本文来自 Derek Molloy 的博客,介绍了内核模块的概念、用途,以及如何构建一个简单的“Hello World”内核模块。

前言

在这系列文章中,将介绍如何为嵌入式 Linux 设备编写 Linux 内核模块。文章将从简单的可加载内核模块(loadable kernel module,LKM)“Hello World!”开始,进而开发通过使用中断请求控制嵌入式 Linux 设备(如 BeagleBone)通用输入输出接口(GPIO)的模块。当我确定合适的应用程序时,我会添加更多的后续文章。

内核模块是一个复杂的话题,需要一定的时间来完成。因此,我将内容拆分成几篇文章,每篇提供一个可以实践的示例和结果。这个话题可以写一整本书,因此很难覆盖每一个方面。关于编写内核模块的其他文章也有很多,而本文的示例都在 Linux 内核 3.8.X 以上版本构建和测试,以确保这些材料是最新且贴切的。同时,本文主要关注嵌入式系统的硬件接口。在我的书《Exploring BeagleBone》中也有相同的示例,由于本文自身包含了这些代码,读者无须拥有该书的副本。

图1:内核空间GPIO 性能

本文集中讨论构建和部署“Hello World!”内核模块所需的系统设置、工具和代码。本系列中的第二篇文章探讨了如何编写字符设备驱动和如何编写用户空间C/C++ 程序与内核空间模块进行交互。第三篇文章探讨内核空间GPIO 库代码的使用,它结合了前两篇文章的内容,开发中断驱动代码,使之能够从Linux 用户空间控制。例如,图1 展示了示波器捕获的通过中断驱动内核模块处理按钮按下到LED 亮起的图形。在常规嵌入式Linux 中(即非实时Linux 的变体),该代码展示忽略CPU 开销后,响应时间大约为20 毫秒(±5 微秒)。

什么是内核模块

可加载内核模块(LKM)是Linux 内核运行时加载和移除代码的机制。该机制对于设备驱动是理想的,这使得内核可以在不知道硬件如何工作的情况下和硬件进行交互。可加载内核模块的替代是将每个驱动代码构建到Linux 内核中。

没有模块化能力,Linux 内核将会变得非常大,因为它不得不支持BeagleBone 开发板上所需的每个驱动。同时,在需要添加新硬件或者升级设备驱动时,必须重新构建内核。可加载内核模块功能的缺点是对于每个设备都必须维护一个驱动文件。可加载内核模块在运行时加载,他们不运行在用户空间,本质上是内核的一部分。

图2:Linux 用户空间和内核空间

如图2 所示,内核模块运行在内核空间,而应用程序运行在用户空间。内核空间和用户空间都有自己独立的内存地址,不会相互重叠。此方法确保了运行在用户空间中的应用程序对于硬件有一致的视图,不用关注硬件平台本身。内核服务通过系统调用以可控的方式提供给用户空间。同时,内核阻止独立的用户空间应用程序之间相互竞争或通过使用保护级别访问受限资源(比如超级用户与普通用户的权限)。

为什么编写内核模块

在嵌入式Linux 中和电子电路交互,你接触到的是系统文件系统,并且使用低级别的文件操作来和电子电路交互。这种方式效率很低(尤其是如果你有传统嵌入式系统开发经验)。然而,对这些文件项进行内存映射后,对于许多应用程序来说性能是足够的。我在书中已经证明,通过在Linux 用户空间使用pthread、回调函数和sys/poll.h,在忽略CPU 开销下,是可以做到约三分之一毫秒的响应时间。

另一个实现是使用内核代码,它支持中断。然而内核代码难以编写和调试。我的建议是优先尝试在Linux 用户空间完成任务,除非已确定没有其他可行方法。

本次讨论的源码

本次讨论的所有代码都在为《Exploring BeagleBone》准备的GitHub 仓库上。代码可以在 ExploringBB GitHub 仓库内核工程目录中公开查看,或者也可以将代码复制到 BeagleBone(或者其他 Linux 设备):

复制代码
molloyd@beaglebone:~$ sudo apt-get install git
molloyd@beaglebone:~$ git clone https://github.com/derekmolloy/exploringBB.git

代码中 /extras/kernel/hello 目录是本文最重要的资源。为这些示例代码自动生成的 Doxygen 文档有 HTML 格式 PDF 格式

准备构建可加载内核模块的系统

为了构建内核代码,需要在设备上安装 Linux 内核头文件。在典型的 Linux 桌面机器上,可以使用包管理器来查找和安装正确的包。例如,在 64 位 Debian 发行版中,可以这样做:

复制代码
molloyd@DebianJessieVM:~$ sudo apt-get update
molloyd@DebianJessieVM:~$ apt-cache search linux-headers-$(uname -r)
linux-headers-3.16.0-4-amd64 - Header files for Linux 3.16.0-4-amd64
molloyd@DebianJessieVM:~$ sudo apt-get install linux-headers-3.16.0-4-amd64
molloyd@DebianJessieVM:~$ cd /usr/src/linux-headers-3.16.0-4-amd64/
molloyd@DebianJessieVM:/usr/src/linux-headers-3.16.0-4-amd64$ ls
arch include Makefile Module.symvers scripts

本系列的前两篇文章的示例,可以在任何桌面 Linux 发行版中完成构建。然而,本系列文章中,我将在 BeagleBone 上直接构建内核模块,这相比于交叉编译可以简化步骤。安装的内核头文件必须和内核构建版本一致。和桌面版安装类似,使用uname命令来识别正确的安装版本。例如:

复制代码
molloyd@beaglebone:~$ uname -a
Linux beaglebone 3.8.13-bone70 #1 SMP Fri Jan 23 02:15:42 UTC 2015 armv7l GNU/Linux

BeagleBone 平台的 Linux 内核头文件可以从 Robert Nelson 的网站下载。比如在 http://rcn-ee.net/deb/precise-armhf/ ,选择准确的内核构建版本,并且在 BeagleBone 上下载和安装这些 Linux 内核头文件。例如:

复制代码
molloyd@beaglebone:~/tmp$ wget http://rcn-ee.net/deb/precise-armhf/v3.8.13-bone70
/linux-headers-3.8.13-bone70_1precise_armhf.deb
100%[===========================>] 8,451,080 2.52M/s in 3.2s
2015-03-17 22:35:45 (2.52 MB/s) - 'linux-headers-3.8.13-bone70_1precise_armhf.deb' saved [8451080/8451080]
molloyd@beaglebone:~/tmp$ sudo dpkg -i ./linux-headers-3.8.13-bone70_1precise_armhf.deb
Selecting previously unselected package linux-headers-3.8.13-bone70

然后可以检查头文件是否正确安装:

复制代码
molloyd@beaglebone:~/tmp$ cd /usr/src/linux-headers-3.8.13-bone70/
molloyd@beaglebone:/usr/src/linux-headers-3.8.13-bone70$ ls
Documentation Module.symvers crypto fs ipc mm scripts tools
Kconfig arch drivers include kernel net security usr
Makefile block firmware init lib samples sound virt

给 BeagleBone 使用的 3.8.13-bone47 版本内核的 Debian 发行版中,需要执行一个特殊步骤在 /usr/src/linux-headers-3.8.13-bone47/arch/arm/include/mach 目录中创建一个空的 timex.h 文件(即 touch timex.h)。bone70 构建不需要此步骤。

警告

编写和测试内核模块时很容易使系统崩溃。系统崩溃可能会损坏文件系统。虽然系统崩溃不太常见,但这是可能发生的。请备份数据或者使用一个嵌入式系统,如 BeagleBone,他们能够很方便的被重新刷写。通过执行 sudo reboot 或者按 BeagleBone 上的重置按钮,通常能够恢复到正常状态。在写本系列文章过程中,尽管有很多很多次系统崩溃,但 BeagleBones 并没有损坏过。

模块代码

传统计算机程序的运行生命周期相当简单。加载器为程序分配内存,然后加载程序和所需要的动态链接库。指令从一些入口开始执行(传统 C/C++ 程序以 main() 函数作为入口),语句被执行,异常被抛出,动态内存被分配和释放,程序最终运行完成。当程序退出时,操作系统识别任何内存泄露,并释放到内存池。

内核模块不是应用程序,从一开始就没有 main() 函数。内核模块和普通应用程序的区别有:

  • 非顺序执行:内核模块使用初始化函数将自身注册并处理请求,初始化函数运行后就结束了。内核模块处理的请求在模块代码中定义。这和常用于图形用户界面(graphical-user interface,GUI)应用的事件驱动编程模型比较类似。
  • 没有自动清理:任何由内核模块申请的内存,必须要模块卸载时手动释放,否则这些内存将无法使用,直到系统重启。
  • 不要使用 printf() 函数:内核代码无法访问为 Linux 用户空间编写的库。内核模块运行在内核空间,它有自己独立的地址空间。内核空间和用户空间的接口被清晰的定义和控制。内核模块可以通过 printk() 函数输出信息,这些输出可以在用户空间查看到。
  • 会被中断:内核模块一个概念上困难的地方在于他们可能会同时被多个程序 / 进程使用。构建内核模块时需要小心,以确保在发生中断的时候行为一致和正确。BeagleBone 有一个单核处理器(目前为止),但是我们仍然需要考虑多进程同时访问对模块的影响。
  • 更高级的执行特权:通常内核模块会比用户空间程序分配更多的 CPU 周期。这看上去是一个优势,然而需要特别注意内核模块不会影响到系统的综合性能。
  • 无浮点支持:对用户空间应用,内核代码使用陷阱(trap)来实现整数到浮点模式的转换。然而在内核空间中这些陷阱难以使用。替代方案是手工保存和恢复浮点运算,这是最好的避免方式,并将处理留给用户空间代码。

以上概念有很多需要消化,重要的是,它们都被解决,但是没有都包含在第一篇文章中。列表 1 提供了第一个示例内核模块的的代码。当没有提供内核参数时,代码使用 printk() 函数显示“Hello world!..”,如果提供了参数“Derek”,日志会显示“Hello Derek!..”。列表 1 中的注释使用 Doxygen 样式,描述每个语句角色。更多的描述在代码列表下放。

复制代码
/**
* @file hello.c
* @author Derek Molloy
* @date 4 April 2015
* @version 0.1
* @brief 入门的可加载内核模块“Hello World!”,当模块加载和移除的时候,会在 /var/log/kern.log 文件输出消息。
* 该模块在加载的时候接受一个参数:名字,它将显示在内核日志文件中。
* @see http://www.derekmolloy.ie/ 查看完整描述和补充描述。
*/
#include <linux/init.h> // 用于标记函数的宏,如 __init、__exit
#include <linux/module.h> // 加载内核模块到内核使用的核心头文件
#include <linux/kernel.h> // 包含内核使用的类型、宏和函数
MODULE_LICENSE("GPL"); ///< 许可类型,它会影响到运行时行为
MODULE_AUTHOR("Derek Molloy"); ///< 作者,当使用 modinfo 命令时可见
MODULE_DESCRIPTION("A simple Linux driver for the BBB."); ///< 模块描述,参见 modinfo 命令
MODULE_VERSION("0.1"); ///< 模块版本
static char *name = "world"; ///< 可加载内核模块参数示例,这里默认值设置为“world”
module_param(name, charp, S_IRUGO); ///< 参数描述。charp 表示字符指针(char ptr),S_IRUGO 表示该参数只读,无法修改
MODULE_PARM_DESC(name, "The name to display in /var/log/kern.log"); ///< 参数描述
/** @brief 可加载内核模块初始化函数
* static 关键字限制了该函数的可见范围为当前 C 文件。
* __init 宏表示对于内置驱动(不是可加载内核模块),该函数只在初始化的时候执行,
* 在此之后,该函数可以废弃,且内存可以被回收。
* @return 当执行成功返回 0
*/
static int __init helloBBB_init(void){
printk(KERN_INFO "EBB: Hello %s from the BBB LKM!\n", name);
return 0;
}
/** @brief 可加载内核模块清理函数
* 和初始化函数类似,它是静态(static)的。__exit 函数表示如果这个代码是给内置驱动(非可加载内核模块)使用,该方法是不需要的。
*/
static void __exit helloBBB_exit(void){
printk(KERN_INFO "EBB: Goodbye %s from the BBB LKM!\n", name);
}
/** @brief 内核模块必须使用 linux/init.h 头文件提供的 module_init() 和 module_exit() 宏,
* 它们标识了在模块插入时的初始化函数和移除时的清理函数(如上描述)
*/
module_init(helloBBB_init);
module_exit(helloBBB_exit);

列表 1:Hello World Linux 可加载内核模块代码

除了列表 1 注释中描述的点之外,还有一些补充的点:

  • 第 16 行:语句 MODULE_LICENSE(“GPL”) 提供了(通过 modinfo)该模块的许可条款,这让使用这个内核模块的用户能够确保在使用自由软件。由于内核是基于 GPL 发布的,许可的选择会影响内核处理模块的方式。如果对于非 GPL 代码选择“专有”许可,内核将会把模块标记为“污染的(tainted)”,并且显示警告。对 GPL 有非污染(non-tainted)的替代品,比如“GPL 版本 2”、“GPL 和附加权利”、“BSD/GPL 双许可”、“MIT/GPL 双许可”和“MPL/GPL 双许可”。更多内容可以查看 linux/module.h 头文件。
  • 第 21 行:名字(字符类型指针)被声明为静态,并且被初始化包含字符串“hello”。在内核模块中应该避免使用全局变量,这比在应用程序编程时更加重要,因为全局变量被整个内核共享。应该使用 static 关键字来限制变量在模块中的作用域。如果必须使用全局变量,在变量名上增加前缀确保在模块中是唯一的。
  • 第 22 行:module_param(name, type, permissions) 宏有三个参数,名字(展示给用户的参数名和模块中的变量名)、类型(参数类型,即 byte、int、uint、long、ulong、short、ushort、bool、逆布尔 invbool 或字符指针之一)和权限(这是当使用 sysfs 时对参数的访问权限。值 0 禁用该项,而值为 S_IRUGO 运行用户 / 组 / 其他有读权限,参阅访问权限模式位指南)。
  • 第 31 和 40 行:函数可以是任何名字(如 helloBBB_init() 和 helloBBB_exit()),但是必须向 module_init() 和 module_exit() 宏传入相同的名字,如第 48 和 49 行。
  • 第 31 行:printk() 和 printf() 行数的使用方式类似,可以在内核模块代码的任何地方调用该函数。唯一重要却别是当调用 printk() 函数时,必须提供日志级别。日志级别在 linux/kern_levels.h 头文件中定义,它的值为 KERN_EMERG、KERN_ALERT、KERN_CRIT、KERN_ERR、KERN_WARNING、KERN_NOTICE、KERN_INFO、KERN_DEBUG 和 KERN_DEFAULT 之一。该头文件通过 linux/printk.h 文件被包含在 linux/kernel.h 头文件中。

从本质上讲,当模块加载时,helloBBB_init() 函数将会执行。当模块卸载时,helloBBB_exit() 函数会被执行。

下一步是将代码构建成内核模块。

构建模块代码

构建内核模块需要 Makefile 文件,事实上是一个特殊的 kbuild Makefile。构建本文示例的内核模块所需要的 kbuild Makefile 文件参见列表 2.

复制代码
obj-m+=hello.o
all:
make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) clean

列表 2:构建 Hello World 可加载内核模块需要的 Makefile 文件

Makefile 文件第一行被成为目标定义,它定义了需要构建的模块(hello.o)。它的语法惊人的复杂,例如 obj-m 定义了可加载模块目标,obj-y 表示内置的对象目标。当模块需要从多个目标文件构建时,语法会变得更加复杂,但这个 Makefile 文件对构建示例模块已经足够了。

Makefile 文件中需要提醒的内容和普通 Makefile 文件类似。$(shell uname -r) 命令返回当前内核构建版本,这确保了一定程度的可移植性。-C 选项在执行任何 make 任务前将目录切换到内核目录。M=$(PWD) 变量赋值告诉make命令实际工程文件存放位置。对于外部内核模块来说,modules 目标是默认目标。另一种目标是 modules_install,它将安装模块(make命令必须使用超级用户权限执行且需要提供模块安装路径)。

一切都很顺利的情况下(如已经按照前文描述安装了 Linux 内核头文件),构建内核模块的过程应该是非常简单的。构建步骤如下:

复制代码
molloyd@beaglebone:~/exploringBB/extras/kernel/hello$ ls -l
total 8
-rw-r--r-- 1 molloyd molloyd 154 Mar 17 17:47 Makefile
-rw-r--r-- 1 molloyd molloyd 2288 Apr 4 23:26 hello.c
molloyd@beaglebone:~/exploringBB/extras/kernel/hello$ make
make -C /lib/modules/3.8.13-bone70/build/ M=/home/molloyd/exploringBB/extras/kernel/hello modules
make[1]: Entering directory '/usr/src/linux-headers-3.8.13-bone70'
CC [M] /home/molloyd/exploringBB/extras/kernel/hello/hello.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/molloyd/exploringBB/extras/kernel/hello/hello.mod.o
LD [M] /home/molloyd/exploringBB/extras/kernel/hello/hello.ko
make[1]: Leaving directory '/usr/src/linux-headers-3.8.13-bone70'
molloyd@beaglebone:~/exploringBB/extras/kernel/hello$ ls
Makefile Module.symvers hello.c hello.ko hello.mod.c hello.mod.o hello.o modules.order

现在,在构建目录中能够看见一个 hello 可加载内核模块,它的文件扩展名为.ko。

测试可加载内核模块

该模块目前能够使用内核模块工具加载:

复制代码
molloyd@beaglebone:~/exploringBB/extras/kernel/hello$ ls -l *.ko
-rw-r--r-- 1 molloyd molloyd 4219 Apr 4 23:27 hello.ko
molloyd@beaglebone:~/exploringBB/extras/kernel/hello$ sudo insmod hello.ko
molloyd@beaglebone:~/exploringBB/extras/kernel/hello$ lsmod
Module Size Used by
hello 972 0
g_multi 50407 2
libcomposite 15028 1 g_multi
omap_rng 4062 0
mt7601Usta 639170 0

通过modinfo命令,可以获得模块的信息,这个命令能够识别出模块的描述、作者和定义的任何模块参数:

复制代码
molloyd@beaglebone:~/exploringBB/extras/kernel/hello$ modinfo hello.ko
filename: /home/molloyd/exploringBB/extras/kernel/hello/hello.ko
description: A simple Linux driver for the BBB.
author: Derek Molloy
license: GPL
srcversion: 9E3F5ECAB0272E3314BEF96
depends:
vermagic: 3.8.13-bone70 SMP mod_unload modversions ARMv7 thumb2 p2v8
parm: name:The name to display in /var/log/kernel.log. (charp)

模块可以通过rmmod命令卸载:

molloyd@beaglebone:~/exploringBB/extras/kernel/hello$ sudo rmmod hello.ko重复上述步骤,可以在内核日志中看见使用 printk() 函数输出的结果。建议使用第二个中断窗口查看这个可加载内核模块加载和卸载时的输出,比如:

复制代码
molloyd@beaglebone:~$ sudo su -
[sudo] password for molloyd:
root@beaglebone:~# cd /var/log
root@beaglebone:/var/log# tail -f kern.log
...
Apr 4 23:34:32 beaglebone kernel: [21613.495523] EBB: Hello world from the BBB LKM!
Apr 4 23:35:17 beaglebone kernel: [21658.306647] EBB: Goodbye world from the BBB LKM!
^C
root@beaglebone:/var/log#
测试可加载内核模块自定义参数

列表 1 中的代码同时包含了自定义参数,它允许在初始化时向内核模块传递参数。这个功能能够这样测试:

molloyd@beaglebone:~/exploringBB/extras/kernel/hello$ sudo insmod hello.ko name=Derek这时如果查看/var/log/kern.log文件,会看见“Hello Derek”替换了“Hello world”。不过,首先要来看下 /proc 和 /sys 文件系统。

除了使用lsmod命令,还能够通过如下方式查找当前系统已经加载的内核模块:

复制代码
molloyd@beaglebone:~/exploringBB/extras/kernel/hello$ cd /proc
molloyd@beaglebone:/proc$ cat modules|grep hello
hello 972 0 - Live 0xbf903000 (O)

这里查看到的信息和lsmod命令提供的相同,但是它同时提供了已加载模块在当前内核内存中的偏移量,这个数据在调试时非常有用。

可加载内核模块在 /sys/module 目录下也有目录项,它提供了用户直接访问自定义参数状态的方式。例如:

复制代码
molloyd@beaglebone:/proc$ cd /sys/module
molloyd@beaglebone:/sys/module$ ls -l|grep hello
drwxr-xr-x 6 root root 0 Apr 5 00:02 hello
molloyd@beaglebone:/sys/module$ cd hello
molloyd@beaglebone:/sys/module/hello$ ls -l
total 0
-r--r--r-- 1 root root 4096 Apr 5 00:03 coresize
drwxr-xr-x 2 root root 0 Apr 5 00:03 holders
-r--r--r-- 1 root root 4096 Apr 5 00:03 initsize
-r--r--r-- 1 root root 4096 Apr 5 00:03 initstate
drwxr-xr-x 2 root root 0 Apr 5 00:03 notes
drwxr-xr-x 2 root root 0 Apr 5 00:03 parameters
-r--r--r-- 1 root root 4096 Apr 5 00:03 refcnt
drwxr-xr-x 2 root root 0 Apr 5 00:03 sections
-r--r--r-- 1 root root 4096 Apr 5 00:03 srcversion
-r--r--r-- 1 root root 4096 Apr 5 00:03 taint
--w------- 1 root root 4096 Apr 5 00:02 uevent
-r--r--r-- 1 root root 4096 Apr 5 00:02 version
molloyd@beaglebone:/sys/module/hello$ cat version
0.1
molloyd@beaglebone:/sys/module/hello$ cat taint
O

这里的版本值为 0.1,对应源码中的 MODULE_VERSION(“0.1”);taint 值为 0,对应所选择的许可,代码中是 MODULE_LICENSE(“GPL”)。

自定义参数查看步骤为:

复制代码
molloyd@beaglebone:/sys/module/hello$ cd parameters/
molloyd@beaglebone:/sys/module/hello/parameters$ ls -l
total 0
-r--r--r-- 1 root root 4096 Apr 5 00:03 name
molloyd@beaglebone:/sys/module/hello/parameters$ cat name
Derek

这里 name 变量的状态可以查看到,并且读取这个值不需要超级用户权限。这是因为在定义内核参数的时候使用了 S_IRUGO 参数。这个值还能够设置为可写,但是模块代码中将会需要检测状态变化并依据变化做出响应。最后,可以移除模块并观察输出:

molloyd@beaglebone:/sys/module/hello/parameters$ sudo rmmod hello.ko正如预期那样,这回在内核日志中有如下输出信息:

复制代码
root@beaglebone:/var/log# tail -f kern.log
Apr 5 00:02:20 beaglebone kernel: [23281.070193] EBB: Hello Derek from the BBB LKM!
Apr 5 00:08:18 beaglebone kernel: [23639.160009] EBB: Goodbye Derek from the BBB LKM!

总结

希望根据此文读者能够构建第一个可加载内核模块。尽管这个模块功能非常简单,它覆盖了大量材料。到了本文的最后,读者应该对可加载内核模块如何工作有了概要认识,应该已经配置了构建系统,构建、加载、卸载了内核模块,有能力为自己的可加载内核模块自定义参数。

下一步的构建工作是通过开发一个基础字符设备驱动,让内核空间可加载内核模块能够和用户空间 C/C++ 应用通信。请阅读《编写Linux 内核模块——第二部分:字符设备驱动》,然后是更加感兴趣的任务:和通用输入输出接口交互。

查看英文原文: Writing a Linux Kernel Module — Part 1: Introduction

编后语

《他山之石》是 InfoQ 中文站新推出的一个专栏,精选来自国内外技术社区和个人博客上的技术文章,让更多的读者朋友受益,本栏目转载的内容都经过原作者授权。文章推荐可以发送邮件到 editors@cn.infoq.com。


感谢魏星对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入 InfoQ 读者交流群)。

2015 年 10 月 26 日 18:108999

评论

发布
暂无评论
发现更多内容

从物理空间到数字世界,数字孪生打造智能化基础设施

华为云开发者社区

IoT 智能 数字

学习笔记-week12

张荣召

以太公约系统开发详情丨以太公约源码案例

系统开发咨询1357O98O718

以太公约系统开发介绍

GaussDB(DWS)磁盘维护:vacuum full执行慢怎么办?

华为云开发者社区

数据库 数据 DWS

12.6大数据仓库Hive

张荣召

第八周总结

小兵

12.2分布式文件系统

张荣召

Java中CAS原理分析(volatile和synchronized浅析)

叫练

volatile 多线程 synchronized CAS JUC

区块链版权应用开发,区块链版权保护解决方案

135深圳3055源中瑞8032

智慧警务大数据决策指挥平台开发,智慧微警务系统搭建

135深圳3055源中瑞8032

排查指南 | 关于 mPaaS-iOS 小程序打不开问题的解决方案

蚂蚁集团移动开发平台 mPaaS

小程序 mPaaS

架构探索:事务处理二

李海明

忒棒了!阿里P8大牛用这份技术点直接带你玩转高可用服务架构

比伯

Java 编程 架构 互联网 程序人生

架构师训练营第 1 期 第 12 周作业

李循律

极客大学架构师训练营

话题讨论 | 作为程序员你的业余爱好是什么呢?

小天同学

话题讨论 业余爱好

英特尔唐炯:36.4% PC同比增长,预示了2021是个好年

新闻科技资讯

ICT芯矿链挖矿矿机系统开发平台丨ICT芯矿链源码案例

系统开发咨询1357O98O718

ICT芯矿链矿机系统开发

12.7作业

张荣召

DolphinDB与Aliyun HybridDB for PostgreSQL在金融数据集上的比较

DolphinDB

postgresql 阿里云 时序数据库 DolphinDB 数据库开发

第五周作业第1题

走走,停停……

年轻程序员不讲武德,做表竟然拖拉拽

雯雯写代码

程序员

12.5大数据集群资源管理系统Yarn

张荣召

12.3大数据计算框架MapReduce-编程框架

张荣召

Eclipse Vert.x 4发布

dinstone

Java Reactive Vert.x

架构探索:事务处理三

李海明

12.4大数据计算框架MapReduce-架构

张荣召

区块链电子合同平台开发,区块链电子合同应用

135深圳3055源中瑞8032

探究神秘的SpringMVC,寻找遗失的web.xml踪迹

996小迁

Java 编程 程序员 架构 面试

12.1大数据技术发展史

张荣召

双十二好物推荐:「mPaaS 安全加固」带你看看别人家的应用

蚂蚁集团移动开发平台 mPaaS

安全 mPaaS 应用

API研发效能提升实战

xiuxiuing

研发效能 API研发

Study Go: From Zero to Hero

Study Go: From Zero to Hero

编写Linux内核模块——第一部分:前言-InfoQ