kk Blog —— 通用基础


date [-d @int|str] [+%s|"+%F %T"]
netstat -ltunp

完整版刷android内核及定制内核模块攻略

blog.sina.com.cn/s/blog_706845a5010129da.html

终于很恶心的把流程走通了

首先列出需要的东西,从网上搜一下就能搜到了:
———源码类———–
1. kernel source
2. .config文件

———工具类———–
1. 交叉编译器arm-eabi-
2. fastboot,adb等android工具
3. mkbootimg用于解包boot.img使用

———脚本类———–
1. repack-bootimg.pl //不一定需要
2. unpack-bootimg.pl

下面是完整版刷内核及内核模块攻略(基于恶心的Galaxy Nexus)
1. 配置交叉编译器等各种环境

1)下载交叉编译器:
$ git clone https://android.googlesource.com/platform/prebuilt

2)写入环境变量中:
export PATH=“/home/xxx/android-toolchain/prebuilt/linux-x86/toolchain/arm-eabi-4.4.3/bin:$PATH”

2. 编译内核

1)修改内核根目录下的Makefile(一劳永逸的方法…):

1
2
3
4
#ARCH           ?= $(SUBARCH)
#CROSS_COMPILE  ?= $(CONFIG_CROSS_COMPILE:"%"=%)
ARCH            ?= arm
CROSS_COMPILE   ?= arm-eabi-

2)从手机目录: /proc/下找到config.gz压缩文件,拿出来解压成.config,复制到内核源码根目录下
很多情况下手机中没有config.gz,我们刷了N多的rom加内核才找到一个能正常跑并且里面有这个文件的内核…
但是找到了可以一直使用,即使换了别的rom或者内核也没关系
实在没有的话,看第三步。
3)如果2)成功了,执行make menuconfig,看看该配置是否支持netfilter,如果不支持安下面的选上

1
2
3
To use netfilter
Networking support  -> Networking options ->  Network packet filtering framework (Netfilter)
Choose related choices

如果找到config.gz,执行make tuna_defconfig(这个是默认的德州仪器CPU的配置文件,理论上可用,但是我没有成功),然后同样看netfilter配置
4)执行make -j 2
5)完成编译,得到arch/arm/boot/zImage文件

3. 将zImage扔到手机中

1)从手机中拿出boot.img,或者从刷入手机的rom或kernel中拿也可,总之拿到一个手机在用的boot.img
2)执行前确保各个脚本permission正确,将boot.img,zImage,脚本unpack-bootimg.pl,可执行文件mkbootimg,放于同一个目录下。
3)执行脚本com.sh:(com.sh内容如下),用于将zImage打包进boot.img形成我们自己的kernel:newtestboot.img

1
2
3
4
5
./unpack-bootimg.pl boot.img
cd boot.img-ramdisk/
find . | cpio -o -H newc | gzip > ../ramdisk-repack.cpio.gz
cd ..       
./mkbootimg --kernel zImage --ramdisk boot.img-ramdisk/ramdisk-repack.cpio.gz --base 0x30000000 -o newtestboot.img
4. 手机进入bootloader模式,利用fastboot刷入newtestboot.img

1)$ adb reboot bootloader
2)$ fastboot boot newtestboot.img
若出现permission denied,waiting for devices之类的问题,执行
$ sudo vim /etc/udev/rules.d/51-android.rules
在规则中添加
若出现permission denied之类的错误,执行
$ sudo vim /etc/udev/rules.d/51-android.rules
在规则中添加:

1
2
    SUBSYSTEM=="usb", ATTRS{idVendor}=="0bb4", MODE="0666"
    SUBSYSTEM=="usb", SYSFS{idVendor}=="18d1", MODE="0666"
这个的作用是将usb权限以及配置与adb或者fastboot配对,特别是fastboot由于是通过usb线刷的,必须保证usb口是匹配的。
5. 此时不出意外就是完成了内核刷入,下面将内核模块加载进去就简单了
6. 编译内核模块

1)利用我们的内核源码作为头文件,交叉编译器作为编译器来编译内核模块,Makefile文件写法如下:

1
2
3
4
5
6
7
8
9
10
11
KERNELDIR := /home/carelife/android_icecream/android_kernel/CyanogenMod
PWD :=$(shell pwd)
ARCH=arm
CROSS_COMPILE=arm-eabi-
CC=$(CROSS_COMPILE)gcc
LD=$(CROSS_COMPILE)ld
obj-m := netCatch.o
modules:
        $(MAKE) -C $(KERNELDIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) modules  
clean:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) clean
7. 加载KM
1
2
3
4
  1)$ adb push /your_kernel_module_position /sdcard/
  2)$ adb shell
  #cd sdcard
  #insmod your_kernel_module_name
8. 查看debug信息

1)$ adb shell dmesg
这个方法的实质是从手机IO缓存中读取print信息,输出到电脑屏幕上,所以是一个固定时间更新的静态查看信息的方法,十分不利于调试
2)等待寻找其他debug方法…

img格式

备份系统中img:dd if=/dev/block/mmcblk0p2 of=/sdcard/boot.img,回车,可得boot.img。

工具

1
2
3
git clone https://github.com/AndroidRoot/BootTools.git BootTools
cd BootTools
make

过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 解压得到 kernel 和 ramdisk,解出来的boot.img-kernel.gz就是zImage
./unpack-bootimg.sh boot.img

# 可以编辑boot.img-ramdisk,编辑好后打包:
cd boot.img-ramdisk
find . | cpio -o -H newc | gzip > ../ramdisk-repack.cpio.gz 

# 查看应加载地址
./hdrboot boot.img

# 重新打包img
./mkbootimg --kernel zImage --ramdisk boot.img-ramdisk.cpio.gz --base 13600000 --ramdisk_offset FF8000 --pagesize 4096 -o new_boot.img

# 查看新加载地址
./hdrboot new_boot.img

注意

打包后的文件用hdrboot看到的一些addr值要和原来一样,一些size的则无所谓

编译GT-S5830内核

下载源码 http://opensource.samsung.com/reception/receptionSub.do?method=sub&sub=F&searchValue=s5830

编译器 https://github.com/AdiPat/Android_Toolchains

编译方法看解开的Kernel的readme。但先注意以下一些再编译:

注意S5830有些驱动,驱动好像是没开源。解开正在用的boot.img,

1
2
$ strings boot.img-ramdisk/lib/modules/fsr.ko | grep vermagic
vermagic=2.6.35.7-perf-CL382966 preempt mod_unload ARMv6

能看到版本为2.6.35.7-perf-CL382966 或者 直接看手机上:设置->关于手机->内核版本。

检查内核的make_kernel_GT-S5830.sh的对应的config(在arch/arm/configs下)文件的CONFIG_LOCALVERSION=XXX,
XXX改成和你手机的这部分'-perf-CL382966'一模一样,不一样这些模块加载不上去,导致开机一直停在三星log那。

编译好后,cp *.ko 到 boot.img-ramdisk/lib/modules/,然后按照 这里 方法重新生成boot.img, 记得zImage用你编译的,在arch/arm/boot/zImage

1
mkbootimg --kernel zImage --ramdisk no_ko_ramdisk.cpio.gz --base 13600000 --ramdisk_offset FF8000 --pagesize 4096 -o 3.4_noko_boot.img

收包软中断和netif_rx

初始化报文接收软中断
1
2
3
4
5
6
static int __init net_dev_init(void)
{
	......
	open_softirq(NET_RX_SOFTIRQ, net_rx_action);
	......
}
报文接收软中断的处理函数net_rx_action详解:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
static void net_rx_action(struct softirq_action *h)
{
	/*取得本地cpu 的softnet_data 的poll_list  链表*/
	struct list_head *list = &__get_cpu_var(softnet_data).poll_list;
	/*设置软中断处理程序一次允许的最大执行时间为2个jiffies*/
	unsigned long time_limit = jiffies + 2;

	/*设置软中断接收函数一次最多处理的报文个数为 300 */
	int budget = netdev_budget;
	/*关闭本地cpu的中断,下面判断list是否为空时防止硬中断抢占*/
	local_irq_disable();
	/*循环处理pool_list 链表上的等待处理的napi*/
	while (!list_empty(list))
	{
		struct napi_struct *n;
		int work, weight;

		/*如果处理报文超出一次处理最大的个数
		  或允许时间超过最大时间就停止执行,
		  跳到softnet_break 处*/
		if (unlikely(budget <= 0 || time_after(jiffies, time_limit)))
		{
			goto softnet_break;
		}
		/*使能本地中断,上面判断list为空已完成,下面调用NAPI
		  的轮询函数是在硬中断开启的情况下执行*/
		local_irq_enable();

		/* 取得softnet_data pool_list 链表上的一个napi,
		   即使现在硬中断抢占软中断,会把一个napi挂到pool_list的尾端
		   软中断只会从pool_list 头部移除一个pool_list,这样不存在临界区*/
		n = list_entry(list->next, struct napi_struct, poll_list);
		/*用weighe 记录napi 一次轮询允许处理的最大报文数*/
		weight = n->weight;
		/* work 记录一个napi总共处理的报文数*/
		work = 0;

		/*如果取得的napi状态是被调度的,就执行napi的轮询处理函数*/
		if (test_bit(NAPI_STATE_SCHED, &n->state))
		{
			work = n->poll(n, weight);
		}
		WARN_ON_ONCE(work > weight);
		/*预算减去已经处理的报文数*/
		budget -= work;
		/*禁止本地CPU 的中断,下面会有把没执行完的NAPI挂到softnet_data
		  尾部的操作,和硬中断存在临界区。同时while循环时判断list是否
		  为空时也要禁止硬中断抢占*/
		local_irq_disable();

		/*如果napi 一次轮询处理的报文数正好等于允许处理的最大数,
		  说明一次轮询没处理完全部需要处理的报文*/
		if (unlikely(work == weight))
		{
			/*如果napi已经被禁用,就把napi 从 softnet_data 的pool_list 上移除*/
			if (unlikely(napi_disable_pending(n)))
			{
				local_irq_enable();
				napi_complete(n);
				local_irq_disable();
			}
			else
			{
				/*否则,把napi 移到 pool_list 的尾端*/
				list_move_tail(&n->poll_list, list);
			}
		}
	}
out:
	local_irq_enable();
	return;

	/*如果处理时间超时,或处理的报文数到了最多允许处理的个数,
	  说明还有napi 上有报文需要处理,调度软中断。
	  否则,说明这次软中断处理完全部的napi上的需要处理的报文,不再需要
	  调度软中断了*/
softnet_break:
	__get_cpu_var(netdev_rx_stat).time_squeeze++;
	__raise_softirq_irqoff(NET_RX_SOFTIRQ);
	goto out;
}
虚拟NAPI backlog 的轮询函数process_backlog():

参数:
napi : 本地cpu上softnet_data 的backlog .
quota : 一次轮询可以处理的最多报文数。

函数详解:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
static int process_backlog(struct napi_struct *napi, int quota)
{
	int work = 0;

	/*取得本地CPU上的softnet_data  数据*/
	struct softnet_data *queue = &__get_cpu_var(softnet_data);

	/*开始计时,一旦允许时间到,就退出轮询*/
	unsigned long start_time = jiffies;
	napi->weight = weight_p;

	/*循环从softnet_data 的输入队列取报文并处理,直到队列中没有报文了,
	 或处理的报文数大于了允许的上限值了,
	 或轮询函数执行时间大于一个jiffies 了
	*/
	do
	{
		struct sk_buff *skb;
		/*禁用本地中断,要存队列中取skb,防止抢占*/
		local_irq_disable();

		/*从softnet_data 的输入队列中取得一个skb*/
		skb = __skb_dequeue(&queue->input_pkt_queue);

		/*如果队列中没有skb,则使能中断并退出轮询*/
		if (!skb)
		{
			/*把napi 从 softnet_data 的 pool_list 链表上摘除*/
			__napi_complete(napi);
			/*使能本地CPU的中断*/
			local_irq_enable();
			break;
		}
		/*skb 已经摘下来了,使能中断*/
		local_irq_enable();

		/*把skb送到协议栈相关协议模块进行处理,详细处理见后续章节*/
		netif_receive_skb(skb);
	} while (++work < quota && jiffies == start_time);
	/*返回处理报文个数*/
	return work;
}
linux旧的收包方式提供给驱动的接口netif_rx():
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
int netif_rx(struct sk_buff *skb)
{
	struct softnet_data *queue;
	unsigned long flags;

	/*如果接收skb的时间戳没设定,设定接收时间戳*/
	if (!skb->tstamp.tv64)
	{
		net_timestamp(skb);
	}

	/*禁止本地cpu的中断*/
	local_irq_save(flags);

	/*取得本地cpu的softnet_data*/
	queue = &__get_cpu_var(softnet_data);
				   
	/*每个CPU都有一个统计数据,增加统计数据*/
	__get_cpu_var(netdev_rx_stat).total++;

	/*如果本地CPU的输入队列中的skb 个数小于允许的最多的个数*/
	if (queue->input_pkt_queue.qlen <= netdev_max_backlog)
	{
		/*如果本地cpu的输入队列长度不为0,表示输入队列已经有skb了,
		并且特殊的napi backlog 已经挂入了softnet_data  的
		pool_list上了*/
		if (queue->input_pkt_queue.qlen)
		{
enqueue:
			/*把skb 放入CPU的输入队列 input_pkt_queue*/
			__skb_queue_tail(&queue->input_pkt_queue, skb);
					  
			/*使能中断 并 返回*/
			local_irq_restore(flags);
			return NET_RX_SUCCESS;
		}
		/*如果输入队列为空,则把 特殊的napi backlog 挂到softnet_data
		的 pool_list 上 并返回把skb放入输入队列并返回*/
		napi_schedule(&queue->backlog);
		goto enqueue;
	}
	/*如果本地cpu的输入队列已经满了,则丢弃报文,
	  并增加丢包计数并返回*/
	__get_cpu_var(netdev_rx_stat).dropped++;
	local_irq_restore(flags);

	kfree_skb(skb);
	return NET_RX_DROP;
}

ruby字符串处理函数

1.返回字符串的长度
1
str.length => integer
2.字符串索引index
1
2
3
4
5
6
7
8
9
10
11
12
13
str.index(substring [, offset])   => fixnum or nil
str.index(fixnum [, offset])      => fixnum or nil
str.index(regexp [, offset])      => fixnum or nil

Returns the index of the first occurrence of the given substring, character (fixnum), 
or pattern (regexp) in str. Returns nil if not found. If the second parameter is present, 
it specifies the position in the string to begin the search.

   "hello".index('e')             #=> 1
   "hello".index('lo')            #=> 3
   "hello".index('a')             #=> nil
   "hello".index(101)             #=> 1
   "hello".index(/[aeiou]/, -3)   #=> 4
从尾到头rindex
1
2
3
4
5
6
7
8
9
10
11
12
13
str.rindex(substring [, fixnum])   => fixnum or nil
str.rindex(fixnum [, fixnum])   => fixnum or nil
str.rindex(regexp [, fixnum])   => fixnum or nil

Returns the index of the last occurrence of the given substring, character (fixnum), 
or pattern (regexp) in str. Returns nil if not found. If the second parameter is present, 
it specifies the position in the string to end the search---characters beyond this point will not be considered.

   "hello".rindex('e')             #=> 1
   "hello".rindex('l')             #=> 3
   "hello".rindex('a')             #=> nil
   "hello".rindex(101)             #=> 1
   "hello".rindex(/[aeiou]/, -2)   #=> 1
3.判断字符串中是否包含另一个串
1
2
3
4
str.include? other_str => true or false
"hello".include? "lo"   #=> true
"hello".include? "ol"   #=> false
"hello".include? ?h     #=> true
4.字符串插入
1
2
3
4
5
6
7
str.insert(index, other_str) => str
"abcd".insert(0, 'X')    #=> "Xabcd"
"abcd".insert(3, 'X')    #=> "abcXd"
"abcd".insert(4, 'X')    #=> "abcdX"
"abcd".insert(-3, 'X')
-3, 'X')   #=> "abXcd"
"abcd".insert(-1, 'X')   #=> "abcdX"
5.字符串分隔,默认分隔符为空格
1
2
3
4
5
6
7
8
9
str.split(pattern=$;, [limit]) => anArray
" now's the time".split        #=> ["now's", "the", "time"]
"1, 2.34,56, 7".split(%r{,\s*}) #=> ["1", "2.34", "56", "7"]
"hello".split(//)               #=> ["h", "e", "l", "l", "o"]
"hello".split(//, 3)            #=> ["h", "e", "llo"]
"hi mom".split(%r{\s*})         #=> ["h", "i", "m", "o", "m"]
"mellow yellow".split("ello")   #=> ["m", "w y", "w"]
"1,2,,3,4,,".split(',')         #=> ["1", "2", "", "3", "4"]
"1,2,,3,4,,".split(',', 4)      #=> ["1", "2", "", "3,4,,"]
6.字符串替换
1
2
3
4
5
str.gsub(pattern, replacement) => new_str
str.gsub(pattern) {|match| block } => new_str
"hello".gsub(/[aeiou]/, '*')              #=> "h*ll*"     #将元音替换成*号
"hello".gsub(/([aeiou])/, '<\1>')         #=> "h<e>ll<o>"   #将元音加上尖括号,\1表示保留原有字符???
"hello".gsub(/./) {|s| s[0].to_s + ' '}   #=> "104 101 108 108 111 "

ruby中带“!“和不带”!“的方法的最大的区别就是带”!"的会改变调用对象本身了。比方说str.gsub(/a/, ‘b’),不会改变str本身,只会返回一个新的str。而str.gsub!(/a/, ‘b’)就会把str本身给改了。
但是gsub和gsub!还有另外一个不同点就是,gsub不管怎么样都会返回一个新的字符串,而gsub!只有在有字符被替换的情况下才会返回一个新的字符串,假如说没有任何字符被替换,gsub!只会返回nil.

字符串替换二:
1
2
3
str.replace(other_str) => str
s = "hello"         #=> "hello"
s.replace "world"   #=> "world"
7.字符串删除:
1
2
3
4
5
str.delete([other_str]+) => new_str
"hello".delete "l","lo"        #=> "heo"
"hello".delete "lo"            #=> "he"
"hello".delete "aeiou", "^e"   #=> "hell"
"hello".delete "ej-m"          #=> "ho"
8.去掉前和后的空格
1
2
3
str.lstrip => new_str
" hello ".lstrip   #=> "hello "
"hello".lstrip       #=> "hello"
9.字符串匹配
1
str.match(pattern) => matchdata or nil
10.字符串反转
1
2
str.reverse => new_str
"stressed".reverse   #=> "desserts"
11.去掉重复的字符
1
2
3
4
str.squeeze([other_str]*) => new_str
"yellow moon".squeeze                  #=> "yelow mon" #默认去掉串中所有重复的字符
" now   is the".squeeze(" ")         #=> " now is the" #去掉串中重复的空格
"putters shoot balls".squeeze("m-z")   #=> "puters shot balls" #去掉指定范围内的重复字符
12.转化成数字
1
2
str.to_i=> str
"12345".to_i             #=> 12345