Category: kernel

构建Lava XOLO X900非官方kernel

本文同步自(最佳显示效果请点击):https://zohead.com/archives/xolo-x900-kernel/ 本文目标为 Lava XOLO X900 这一印度咖喱味手机,同样 Intel Atom Z2460 的芯,移植 kernel 的方法和之前的联想 K800 手机基本一致,具体请移步下面的链接: https://zohead.com/archives/k800-kernel-otg-udisk/ 经过确认 XOLO X900 默认也是 Android 4.0.4 ROM,同样 3.0.8 kernel,当然硬件会有所不同,不过 OTG U盘所需要使用的 usb-storage 模块也是和 K800 一样默认没有开启的。 基于基本相同的 kernel source,先使用 Medfield 默认的配置 i386_mfld_defconfig,发现与现有的 kernel 差距比较多,然后用 Moto 的 i386_mfld_moto_defconfig 编译出的模块加载依然 panic。没办法,硬着头皮与实际运行中 kernel 情况进行对比,发现基于默认配置 i386_mfld_defconfig 进行修改是比较好的方法,而且看得出 XOLO 相对联想还是做了一定的优化,去掉了像 CONFIG_DEBUG_KERNEL 之类的很多调试选项,默认允许了 tun 驱动等。另外声卡相关的配置不同的地方也比较多,经过最终改动和确认,编译之后,先已能成功加载并使用之前在 K800 上编译的那些模块。 XOLO X900 的内核模块和配置文件的下载地址: http://miseal.googlecode.com/files/x900-kernel-config-modules.7z 使用方法和 K800 的基本一致,只是 CIFS 文件系统的使用上有变动: insmod des_generic.ko insmod md4.ko insmod cifs.ko ASIX Electronics 的 USB 以太网卡的驱动 asix.ko 也已经更新到官方最新的版本(kernel 自带的版本无法使用)。 现已基本可以确认我改动之后的 kernel 和 X900 现有 kernel 的相似性已有 90% 或以上了,不过由于 X900 手机悲剧滴将 bootloader 给锁了,无法刷未签名的 kernel,暂时还没有太好的办法来直接替换 kernel 进行测试哦。 另外有一些需要说明的地方: XOLO X900 现有 kernel 中支持 Kineto GAN 虚拟网卡驱动(支持 VOIP 网络电话的),Moto Razr i 公开的 kernel source 中却并没有这个的支持,为此我手工增加了这个的驱动(上面下载的 kernel configuration 中也有的); 目前还有 apwr3_0、pax、sep3_7 这三个非免费开源的内核模块没有源代码,暂时也没有办法在网上找到,不过从现在来看,似乎 X900 实际运行时也未使用这三个模块。 同样我不对任何导致你的手机 panic 挂掉的后果承担责任,有任何问题欢迎提出指正哦 ^_^

构建联想K800非官方kernel支持OTG U盘

本文同步自(最佳显示效果请点击):https://zohead.com/archives/k800-kernel-otg-udisk/ 这几天专门入了个二手的联想 K800 的 Android 手机,看中的就是它是 x86 的 CPU,其采用 Intel Medfield Atom 平台,具体处理器型号为 Intel Atom Z2460,用的 Android 4.0.4 系统,具体其它配置我就不多说的。 优点: 使用 Intel x86 CPU,方便程序的移植(相对 ARM 而言); 4.5 寸的 1280 x 720 的 IPS 高清屏幕,主要分辨率够给力; 支持 USB OTG; 支持 MHL 视频输出(缺点也在这,下面再说); 耗电没想象中的那么严重; 使用的 Intel Atom Z2460 CPU 支持 EM64T 64 位指令; 使用的 Intel Atom Z2460 CPU 支持 Intel VT-x 虚拟化技术(不过这个好像在 Android 4.1 中用处才发挥出来,暂时也用不上); 很便宜。 缺点: 没有单独的 HDMI 接口,视频输出需要占用 MicroUSB,OTG 与 MHL 无法同时使用; OTG 不支持 U 盘(也是本文要解决的); 恶心的乐 Phone,联想没有公开 kernel 源代码。 上面说的缺点里,最后一条不公开 kernel source 是联想一贯悠久的恶劣传统,也是我最忍受不了的一条。 一番努力搜寻之后,我终于找到了曲线救国的神主:Motorola。对,就是他,虽然摩托似乎从来就没有开放的基因,但被 Google 收购之后,摩托在其推出的 Intel 手机 Moto RAZR i 上终于有良心了一把:公布了 Moto RAZR i 的 kernel source。由于 Moto RAZR i 与联想 K800 同样采用 Intel Medfield 平台,因此我就赌了一把其 kernel source 会有很大程度上的相似性,结果很幸运小成功了。 1、获取源码: 首先在 SourceForge 上抓取 Moto RAZR i 的 kernel source: http://sourceforge.net/projects/razr-i.motorola/files/ 一共 100 多MB,咱先解压,打开 Makefile,确定 kernel version 是 3.0.8 版本。 给联想 K800 root,装 adb 驱动,用 adb shell 连上看,运行 uname -a 查看 K800 手机的 kernel version 是 3.0.8-g37de913,啊哈,除了后缀版本一致。然后尝试读取 /proc/config.gz,无奈联想又是没有把 kernel configuration 给导出,找不到 /proc/config.gz 文件,没办法,只能自己根据当前手机 kernel 判断 K800 当前的 kernel configuration 了。 运行:egrep ‘(vmx|svm)’ /proc/cpuinfo,确定 CPU 支持 Intel VT-x 虚拟化技术,有点小兴奋。 2、准备编译环境: 接着在手机上查看 /proc/version,确定联想 K800 kernel 使用的 gcc 版本为 4.3.3,刚好与 Moto 介绍的一致。然后使用 git clone 从下面的地址得到编译 kernel 的 android x86 toolchain: https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/x86/i686-android-linux-4.4.3/ 需要注意 git 得到的代码中默认已经把 gcc 4.3.3 给移除了,不过运行 git log 可以看到之前的提交日志里是有的,那好办,运行下面命令回滚到最新可用的版本: git checkout 286506f01f8ca64d6eb0e33bafb475a5cf10ff37 终于有了编译 kernel 的正确环境了(不是完整开发环境哈)。 3、编译 kernel: Moto 提供的 kernel source 中有几个适合 Intel Medfield 平台的 config 文件,SourceForge 网站上推荐使用 i386_mfld_moto_defconfig,编译时指定 CROSS_COMPILE,但我实际编译之后的模块加载会 kernel panic。好吧,我来尝试应该是 Intel 原始推荐的 i386_mfld_defconfig 默认配置,无法编译通过,排除原因,发现 i386_mfld_defconfig 中启用的几个选项在 K800 中并没有开启,遂决定去掉,同时去掉了 Moto 特有的加密模块。 修改配置之后,能编译了,但编译到 android USB gadget 代码时又有报错,查看代码,发现 Moto 对 android USB gadget 代码做了特殊改动,好吧,这我暂时不需要修改,根据 Moto 增加的 define 修改 drivers/usb/gadget/android.c,去掉 Moto 特有的改动,尽量使代码默认 Intel 原生(看来联想对 Intel 的代码改动很少,这是个好处,嘿嘿,下面也有悲催的地方),再次开始编译。 这次终于比较顺利了,能编译到链接 kernel image 的地方了,但又有报错,这时就不管了,因为我压根就不指望这个编译出来的 zImage 能直接用在联想 K800 手机上,而且联想也没开放制作其 boot image 的工具和方法,就算 zImage 有了也不知道怎么刷到手机上。 接下来编译 kernel modules,这步比较顺利,我把 CIFS、USB Mass Storage(包括 scsi disk 支持)、usbnet、usb serial converter、tun 等需要的模块都选中,都正确编译出来了。 4、修改调试: 拿到手机上加载 cifs.ko,正常,/proc/filesystems 中已经有了 cifs,但挂载文件系统时发现 dmesg 里有 out of memory 报错,一会就 kernel panic 了。 强制重启手机,这时才发现 cifs.ko 大小有 3MB 多,明显太大,查看 config 发现默认启用了 debug kernel,Moto 的默认配置中未启用,不得不说联想真是够懒的,这个影响性能的东西竟然木有去掉。 本来想禁用 […]

使用Linux kernel dm-mirror实现设备间同步

本文同步自(最佳显示效果请点击):https://zohead.com/archives/dm-mirror-sync-device/ 在某些场合下,用户需要在 Linux 系统中实现直接的块设备同步方式,需要将一个裸设备中的内容完全同步到另一个裸设备,有时甚至需要能自定义位移和大小进行同步,这时 Linux 自带的 dm-mirror 模块很可能是比较好的实现方案。 dm-mirror 是 Linux kernel 中 device-mapper(DM)模块(device-mapper 内部称为 target)中的一个,可以实现多个块设备间的数据同步,并支持通过 dm-log 模块保存同步时的元数据信息,方便在关机和重启之后能够继续之前的进度进行同步,而且 dm-mirror 的元数据可以选择保存在内存中或者固定的块设备中。LVM 逻辑卷管理中的 pvmove 命令可以在卷组中替换物理卷,pvmove 命令正是基于 dm-mirror 模块实现的。Linux kernel 中还包含了其它的一些 device-mapper target 模块,例如 dm-linear(用于实现 LVM 逻辑卷管理)、dm-crypt(加密数据)、dm-snapshot(用于实现即时快照)等。 dm-mirror 的主要实现代码在 Linux kernel 代码树的 drivers/md/dm-raid1.c 中,dm-mirror 的日志元数据记录功能通过 dm-log 模块实现,同步的区块划分由 dm-region-hash 模块实现,底层的同步实现则由 kcopyd 来实现。kcopyd 的功能就是从一个块设备拷贝一定扇区的数据到另一个或多个块设备,kcopyd 支持异步的完成通知功能,目前主要被 dm-snapshot 和 dm-mirror 模块使用。有关 Linux kernel 中 kcopyd 的说明请参考这里:http://www.kernel.org/doc/Documentation/device-mapper/kcopyd.txt。 dm-mirror 设备的装载通过 dmsetup 命令来实现,以下说明的地址都是以扇区为单位(固定为 512 个字节),先看看 dm-mirror target 的 table 格式: start length mirror log_type #logargs logarg1 … logargN #devs device1 offset1 … deviceN offsetN [#features <features>] 对每个值分别说明如下: start 为虚拟设备起始扇区地址,一般固定为 0 length 为 dm-mirror 设备的大小,也就是同步的总扇区大小 mirror 值固定,表示这是一个 dm-mirror 设备 log_type 指定日志元数据类型,常用的是 core 和 disk 两种类型 #logargs 表示后面的 logarg1 … logargN 一共有多少个 #devs 表示需要同步的设备个数,每个设备都有一个 device 值和一个 offset 值 deviceN offsetN 为每个需要同步的设备的设备路径(或者设备号)和偏移地址 #features 和 features 为可选的值,#features 表示可选项个数 如果指定了 #features,则必须为 1,且 features 必须为 handle_errors(表示 dm-mirror 会自动进行错误处理,即同步出错时自动停止同步并删除 dm-mirror 虚拟设备) 然后是 core 和 disk 两种日志元数据类型的介绍(另外有 clustered_core 和 clustered_disk 两种适用于集群的日志类型,有兴趣可以自己去了解下哦): core 类型(内存): 日志保存在内存中,关机或者重启之后会丢失,因此安全性较差,但由于元数据存放在内存中,性能相对 disk 类型来说是非常好的。#logargs 个数为 1-2 个,日志参数格式为: regionsize [[no]sync]regionsize 为日志区块的大小(512 个字节的扇区为单位),可选的 sync 和 nosync 用于指定 dm-mirror 是否已经同步过。 disk 类型(磁盘): 日志保存在磁盘(块设备)中,因此可以在关机或重启之后继续之前的同步进度,当然性能比 core 类型肯定是要差一些的。#logargs 个数为 2-3 个,日志参数格式为: logdevice regionsize [[no]sync]logdevice 指定日志元数据存放在哪个块设备上,可以使用设备路径或者主从设备号形式,其它参数与 core 类型相同。 Linux 系统中 dm-mirror 具体使用步骤举例如下: 首先加载 dm-mirror 模块: modprobe dm-mirror  准备好需要同步的设备,假设是实际应用中的一种常见需求:Linux 系统盘为 /dev/sda,系统中另外有个数据盘 /dev/sdb。但由于磁盘 /dev/sdb 寿命快到了,需要将 /dev/sdb 磁盘上的数据同步到 /dev/sdc 上,/dev/sdc 的容量比 /dev/sdb 要大。先得到 /dev/sdb 和 /dev/sdc 的容量大小(以 512 个字节的扇区为单位): blockdev --getsz /dev/sdb blockdev --getsz /dev/sdc假设分别为 209715200(100G) 和 314572800(150G)。 在系统中通过文件方式产生 loop 设备用作 dm-mirror 同步存放日志元数据的设备,防止关机或重启之后同步进度丢失,1MB 的文件已完全足够保存日志元数据:dd if=/dev/zero of=meta.dat bs=1024k count=1运行完成将产生一个 1MB 大小的 meta.dat 文件。 准备 loop 设备:losetup -f 得到空闲的 loop 设备名,假设为 /dev/loop0;losetup /dev/loop0 meta.dat 装载 loop 设备。 运行 dmsetup create 命令创建 dm-mirror 设备进行同步:echo “0 209715200 mirror disk 2 /dev/loop0 16384 2 /dev/sdb 0 /dev/sdc 0 1 handle_errors” | dmsetup create mirror0运行成功之后将产生名称为 mirror0 的虚拟 dm-mirror 设备。echo 中间的字符串即为 dm-mirror 的 table 格式:209715200 为同步的大小,disk 指定元数据保存在磁盘中,/dev/loop0 为元数据设备,16384 为区块大小(这里指定为 8MB 为提高同步性能),后面的参数指定将 /dev/sdb(源设备,起始地址为 0)的数据同步到 /dev/sdc(目标设备,起始地址为 0),1 和 handle_errors 表示不忽略同步错误。 查询同步状态和进度:dmsetup status mirror0输出类似下面:mirror0: 0 209715200 mirror 2 8:16 8:32 417/12800 1 AA 3 disk 7:0 A8:16 […]

Linux kernel学习-进程地址空间

本文同步自(如浏览不正常请点击跳转):https://zohead.com/archives/linux-kernel-learning-process-address-space/ 看完 Linux kernel block I/O 层之后来到进程地址空间管理部分,本文中的很多知识和之前的 [进程基本]、[进程调度]、[内存管理] 等章节的知识相关。 1、基础知识: Linux kernel 给每个进程提供的进程地址空间一般是 32 位或 64 位(硬件相关)的平坦地址空间,但进程是没有权限访问这段地址空间中的所有地址的,能访问的一般是很多的内存地址区间。这种内存地址区间被称为内存区域,进程可以动态添加和删除内存区域到它的地址空间中。内存区域可以有不同的权限,相关进程必须遵守这些权限,例如可读、可写、可执行等。如果进程访问的地址不在一个有效的内存区域中,或者访问时的权限不正确,kernel 将会杀掉进程并给出常见的 “Segmentation Fault” 段错误日志。 内存区域通常包括: 可执行文件的代码段,称为 text 段; 可执行文件的已初始化全局变量段,称为 data 段; 未初始化全局变量段(通常以 0 page 填充),称为 bss 段; 进程的用户空间栈(通常以 0 page 填充); 每个共享库文件的额外 text、data、bss 段,也被装入进程的地址空间; 内存映射文件; 共享内存区域; 匿名内存映射(新版本的 malloc 函数就除了 brk 之外也通过 mmap 实现); 应用程序中的堆 2、内存描述符: kernel 使用 mm_struct 内存描述符结构来表示进程的地址空间信息,它定义在 <linux/mm_types.h> 头文件中,这也是一个非常大的结构。 结构的注释中已经包含比较多的注解了哦。mmap 为地址空间的内存区域(用 vm_area_struct 结构来表示啦,也是上面的代码中)链表,mm_rb 则将其以红黑树的形式进行存储,链表形式方便遍历,红黑树形式方便查找。mm_users 为以原子变量形式保护的使用此地址空间的进程数量值(例如:如果有 4 个线程共享此地址空间,则 mm_users 值为 4),mm_count 为引用计数(所有 mm_users 等于一个引用计数),当 mm_count 值为 0 时表示没有再被使用,可以被释放。total_vm 成员表示所有内存区域的数量。 所有的 mm_struct 结构以链表的形式存在 mm_struct 的 mmlist 成员中,该链表的第一个成员就是 init 进程的 mm_struct :init_mm,该链表被 mmlist_lock 锁保护。 进程的内存描述符是在 task_struct 的 mm 成员中的。fork() 进行创建进程时调用 copy_mm 函数将父进程的内存描述符拷贝给子进程,调用 clone() 函数时如果指定 CLONE_VM 参数将使父进程和子进程地址空间共享(实际上将 mm_users 计数加 1),这种子进程就被称为线程。mm_struct 结构一般是通过 alloc_mm 宏从名为 mm_cachep 的 Slab cache 中分配。 进程退出时调用 exit_mm 函数,该函数再调用 mmput() 函数,此函数中减小地址空间的 mm_users 计数,如果 mm_users 变为 0,调用 mmdrop() 函数减小 mm_count 计数,如果 mm_count 变为 0,则最终调用 free_mm() 宏来释放内存描述符(回归到 Slab cache 中)。 另外需要说明的是 kernel 线程是没有地址空间,也就没有对应的 mm_struct(值为 NULL),kernel 线程使用之前运行的进程的内存描述符,有关 kernel 线程请参考之前的 [进程基本] 文章。 3、VMA 概念: vm_area_struct 结构即内存区域常被称为虚拟内存区域(简写为 VMA),表示的是在一个地址空间中的一个连续内存地址区间,每个内存区域是一个惟一的对象。vm_area_struct 中的 vm_mm 成员指向关联的内存描述符,vm_ops 成员为非常重要的关联的操作函数结构,vm_start 为起始地址,vm_end 为结束地址之后第一个字节的地址,即地址范围为:[vm_start, vm_end)。每个 VMA 对于它关联的内存描述符来说是惟一的,因此如果两个单独的进程映射相同的文件到各自的地址空间,它们的 VMA 也是不同的。 VMA 中的 vm_flags 表示内存区域中的页的行为状态,常见的状态有:VM_READ(页可读)、VM_WRITE(页可写)、VM_EXEC(页可被执行)、VM_SHARED(页被共享,被设置了称为共享映射,未设置称为私有映射)、VM_SHM(此区域被用作共享内存)、VM_LOCKED(页被锁)、VM_IO(此区域用于映射设备 I/O 空间)、VM_RESERVED(表示内存区域不可被交换出去)、VM_SEQ_READ(连续读,增强 readahead)、VM_RAND_READ(随机读,减弱 readahead)等。VM_SEQ_READ 和 VM_RAND_READ 标志可以通过 madvise() 系统调用来设置。 看看 vm_ops 操作函数结构的 vm_operations_struct 的定义,它在 <linux/mm.h> 头文件中: 当指定的内存区域被添加到地址空间时,open 函数被调用,反之移除时 close 函数被调用。如果一个不在内存中的页被访问,将触发缺页异常, fault 函数被缺页异常处理函数调用。当一个只读的页变为可写的时候,page_mkwrite 函数也被缺页异常处理函数调用。 mm_struct 中的 mmap 为内存区域链表,通过 VMA 的 vm_next 成员指向下一个内存区域,而且链表中的内存区域是按地址上升排序的,链表中最后一个 VMA 值为 NULL。而对于 mm_struct 的 mm_rb 红黑树,mm_rb 为红黑树的根,每个 VMA 通过其 vm_rb 红黑树节点类型链到红黑树中。 在应用层中可以通过 cat /proc/<pid>/maps 或者 pmap 程序等方法查看应用程序的内存区域列表。 操作 VMA: kernel 提供 find_vma() 函数用于查找指定的内存地址在哪个 VMA 上,它的实现在 mm/mmap.c 文件中,输入参数为内存描述符和内存地址: 如果找不到对应的 VMA 则返回 NULL。需要注意的是返回的 VMA 的开始地址可能比指定的内存地址大。find_vma() 函数返回的结果会被缓存到内存描述符的 mmap_cache 成员中用于提高之后的查找性能,因为后续的操作很可能还是在同样的 VMA 上。如果在 mmap_cache 中找不到则通过红黑树进行查找。 find_vma_prev() 函数与 find_vma() 函数类似,不过它也会返回指定地址之前的最后一个 VMA: struct vm_area_struct * find_vma_prev(struct mm_struct *mm, unsigned long addr, struct vm_area_struct **pprev) kernel 另外还提供了 find_vma_intersection() 函数返回符合 find_vma() 的条件并且其开始地址不在指定内存结束地址之后的 VMA。 4、mmap 和 munmap: kernel 提供 do_mmap() 函数创建新的线性地址区间,这是用户层 mmap() 函数的底层实现,它用于将一段地址区间添加到进程的地址空间中。 unsigned long do_mmap(struct file *file, unsigned long addr, unsigned long len, unsigned long prot, unsigned long flag, unsigned long offset) do_mmap 映射 file 参数指定的文件,并最终返回新创建的地址区间的初始地址。 offset 和 len 指定偏移量和长度。如果 file 为 […]