kk Blog —— 通用基础

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

Linux内核中的等待队列

http://blog.sina.com.cn/s/blog_49d5604e010008bn.html

等待队列可以参考net/ipv4/tcp_probe.c的实现

简单样例

Linux内核中的等待队列

Linux内核的等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。在Linux2.4.21中,等待队列在源代码树include/linux/wait.h中,这是一个通过list_head连接的典型双循环链表,

如下图所示。

在这个链表中,有两种数据结构:等待队列头(wait_queue_head_t)和等待队列项(wait_queue_t)。等待队列头和等待队列项中都包含一个list_head类型的域作为"连接件"。由于我们只需要对队列进行添加和删除操作,并不会修改其中的对象(等待队列项),因此,我们只需要提供一把保护整个基础设施和所有对象的锁,这把锁保存在等待队列头中,为wq_lock_t类型。在实现中,可以支持读写锁(rwlock)或自旋锁(spinlock)两种类型,通过一个宏定义来切换。如果使用读写锁,将wq_lock_t定义为rwlock_t类型;如果是自旋锁,将wq_lock_t定义为spinlock_t类型。无论哪种情况,分别相应设置wq_read_lock、wq_read_unlock、wq_read_lock_irqsave、wq_read_unlock_irqrestore、wq_write_lock_irq、wq_write_unlock、wq_write_lock_irqsave和wq_write_unlock_irqrestore等宏。

等待队列头
1
2
3
4
5
struct __wait_queue_head {
	wq_lock_t lock;
	struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;

前面已经说过,等待队列的主体是进程,这反映在每个等待队列项中,是一个任务结构指针(struct task_struct * task)。flags为该进程的等待标志,当前只支持互斥。

等待队列项
1
2
3
4
5
6
7
struct __wait_queue {
	unsigned int flags;
#define WQ_FLAG_EXCLUSIVE 0x01
	struct task_struct * task;
	struct list_head task_list;
};
typedef struct __wait_queue wait_queue_t;
声明和初始化
1
2
3
4
5
6
#define DECLARE_WAITQUEUE(name, tsk)            \
	wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)
#define __WAITQUEUE_INITIALIZER(name, tsk) {    \
	task:  tsk,                                 \
	task_list: { NULL, NULL },                  \
	__WAITQUEUE_DEBUG_INIT(name)}

通过DECLARE_WAITQUEUE宏将等待队列项初始化成对应的任务结构,并且用于连接的相关指针均设置为空。其中加入了调试相关代码。

1
2
3
4
5
6
#define DECLARE_WAIT_QUEUE_HEAD(name)                    \
	wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
#define __WAIT_QUEUE_HEAD_INITIALIZER(name) {            \
	lock:  WAITQUEUE_RW_LOCK_UNLOCKED,                   \
	task_list: { &(name).task_list, &(name).task_list }, \
	__WAITQUEUE_HEAD_DEBUG_INIT(name)}

通过DECLARE_WAIT_QUEUE_HEAD宏初始化一个等待队列头,使得其所在链表为空,并设置链表为"未上锁"状态。其中加入了调试相关代码。

1
static inline void init_waitqueue_head(wait_queue_head_t *q)

该函数初始化一个已经存在的等待队列头,它将整个队列设置为"未上锁"状态,并将链表指针prev和next指向它自身。

1
2
3
4
5
{
	q->lock = WAITQUEUE_RW_LOCK_UNLOCKED;
	INIT_LIST_HEAD(&q->task_list);
}
static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)

该函数初始化一个已经存在的等待队列项,它设置对应的任务结构,同时将标志位清0。

1
2
3
4
5
{
	q->flags = 0;
	q->task = p;
}
static inline int waitqueue_active(wait_queue_head_t *q)

该函数检查等待队列是否为空。

1
2
3
4
{
	return !list_empty(&q->task_list);
}
static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)

将指定的等待队列项new添加到等待队列头head所在的链表头部,该函数假设已经获得锁。

1
2
3
4
{
	list_add(&new->task_list, &head->task_list);
}
static inline void __add_wait_queue_tail(wait_queue_head_t *head, wait_queue_t *new)

将指定的等待队列项new添加到等待队列头head所在的链表尾部,该函数假设已经获得锁。

1
2
3
4
{
	list_add_tail(&new->task_list, &head->task_list);
}
static inline void __remove_wait_queue(wait_queue_head_t *head, wait_queue_t *old)

将函数从等待队列头head所在的链表中删除指定等待队列项old,该函数假设已经获得锁,并且old在head所在链表中。

1
2
3
{
	list_del(&old->task_list);
}

睡眠和唤醒操作

对等待队列的操作包括睡眠和唤醒(相关函数保存在源代码树的/kernel/sched.c和include/linux/sched.h中)。思想是更改当前进程(CURRENT)的任务状态,并要求重新调度,因为这时这个进程的状态已经改变,不再在调度表的就绪队列中,因此无法再获得执行机会,进入"睡眠"状态,直至被"唤醒",即其任务状态重新被修改回就绪态。

常用的睡眠操作有interruptible_sleep_on和sleep_on。两个函数类似,只不过前者将进程的状态从就绪态(TASK_RUNNING)设置为TASK_INTERRUPTIBLE,允许通过发送signal唤醒它(即可中断的睡眠状态);而后者将进程的状态设置为TASK_UNINTERRUPTIBLE,在这种状态下,不接收任何singal。

以interruptible_sleep_on为例,其展开后的代码是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void interruptible_sleep_on(wait_queue_head_t *q)
{
	unsigned long flags;
	wait_queue_t wait;
	/* 构造当前进程对应的等待队列项 */
	init_waitqueue_entry(&wait, current);

	/* 将当前进程的状态从TASK_RUNNING改为TASK_INTERRUPTIBLE */
	current->state = TASK_INTERRUPTIBLE;

	/* 将等待队列项添加到指定链表中 */
	wq_write_lock_irqsave(&q->lock,flags);
	__add_wait_queue(q, &wait); 
	wq_write_unlock(&q->lock);

	/* 进程重新调度,放弃执行权 */
	schedule();

	/* 本进程被唤醒,重新获得执行权,首要之事是将等待队列项从链表中删除 */
	wq_write_lock_irq(&q->lock);
	__remove_wait_queue(q, &wait);
	wq_write_unlock_irqrestore(&q->lock,flags);
	/* 至此,等待过程结束,本进程可以正常执行下面的逻辑 */
}

对应的唤醒操作包括wake_up_interruptible和wake_up。wake_up函数不仅可以唤醒状态为TASK_UNINTERRUPTIBLE的进程,而且可以唤醒状态为TASK_INTERRUPTIBLE的进程。

wake_up_interruptible只负责唤醒状态为TASK_INTERRUPTIBLE的进程。这两个宏的定义如下:

1
2
#define wake_up(x)   __wake_up((x),TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1)
#define wake_up_interruptible(x) __wake_up((x),TASK_INTERRUPTIBLE, 1)

wake_up函数主要是获取队列操作的锁,具体工作是调用wake_up_common完成的。

1
2
3
4
5
6
7
8
9
void __wake_up(wait_queue_head_t *q, unsigned int mode, int nr)
{
	if (q) {
	    unsigned long flags;
	    wq_read_lock_irqsave(&q->lock, flags);
	    __wake_up_common(q, mode, nr, 0);
	    wq_read_unlock_irqrestore(&q->lock, flags);
	}
}

/ The core wakeup function. Non-exclusive wakeups (nr_exclusive == 0) just wake everything up. If it’s an exclusive wakeup (nr_exclusive == small +ve number) then we wake all the non-exclusive tasks and one exclusive task. There are circumstances in which we can try to wake a task which has already started to run but is not in state TASK_RUNNING. try_to_wake_up() returns zero in this (rare) case, and we handle it by contonuing to scan the queue. /

1
static inline void __wake_up_common (wait_queue_head_t *q, unsigned int mode, int nr_exclusive, const int sync)

参数q表示要操作的等待队列,mode表示要唤醒任务的状态,如TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBLE等。nr_exclusive是要唤醒的互斥进程数目,在这之前遇到的非互斥进程将被无条件唤醒。sync表示???

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
{
	struct list_head *tmp;
	struct task_struct *p;

	CHECK_MAGIC_WQHEAD(q);
	WQ_CHECK_LIST_HEAD(&q->task_list);

	/* 遍历等待队列 */
	list_for_each(tmp,&q->task_list) {
	    unsigned int state;
	    /* 获得当前等待队列项 */
	    wait_queue_t *curr = list_entry(tmp, wait_queue_t, task_list);

	    CHECK_MAGIC(curr->__magic);
	    /* 获得对应的进程 */
	    p = curr->task;
	    state = p->state;

	    /* 如果我们需要处理这种状态的进程 */
	    if (state & mode) {
	        WQ_NOTE_WAKER(curr);
	        if (try_to_wake_up(p, sync) && (curr->flags&WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
	            break;
	    }
	}
}

/ 唤醒一个进程,将它放到运行队列中,如果它还不在运行队列的话。"当前"进程总是在运行队列中的(except when the actual re-schedule is in progress),and as such you’re allowed to do the simpler “current->state = TASK_RUNNING” to mark yourself runnable without the overhead of this. /

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static inline int try_to_wake_up(struct task_struct * p, int synchronous)
{
	unsigned long flags;
	int success = 0;

	/* 由于我们需要操作运行队列,必须获得对应的锁 */
	spin_lock_irqsave(&runqueue_lock, flags);
	/* 将进程状态设置为TASK_RUNNING */
	p->state = TASK_RUNNING;
	/* 如果进程已经在运行队列中,释放锁退出 */
	if (task_on_runqueue(p))
	    goto out;
	/* 否则将进程添加到运行队列中 */
	add_to_runqueue(p);

	/* 如果设置了同步标志 */
	if (!synchronous || !(p->cpus_allowed & (1UL << smp_processor_id())))
	    reschedule_idle(p);
	/* 唤醒成功,释放锁退出 */
	success = 1;
out:
	spin_unlock_irqrestore(&runqueue_lock, flags);
	return success;
}

等待队列应用模式

等待队列的的应用涉及两个进程,假设为A和B。A是资源的消费者,B是资源的生产者。A在消费的时候必须确保资源已经生产出来,为此定义一个资源等待队列。这个队列同时要被进程A和进程B使用,我们可以将它定义为一个全局变量。

1
DECLARE_WAIT_QUEUE_HEAD(rsc_queue); /* 全局变量 */

在进程A中,执行逻辑如下:

1
2
3
4
while (resource is unavaiable) {
	interruptible_sleep_on( &wq );
}
consume_resource();

在进程B中,执行逻辑如下:

1
2
produce_resource();
wake_up_interruptible( &wq );

字符设备驱动程序

http://techlife.blog.51cto.com/212583/39225

简单样例

实现如下的功能:
-字符设备驱动程序的结构及驱动程序需要实现的系统调用
-可以使用cat命令或者自编的readtest命令读出"设备"里的内容
-以8139网卡为例,演示了I/O端口和I/O内存的使用

本文中的大部分内容在Linux Device Driver这本书中都可以找到,这本书是Linux驱动开发者的唯一圣经。


先来看看整个驱动程序的入口,是char8139_init()这个函数,如果不指定MODULE_LICENSE(“GPL”), 在模块插入内核的时候会出错,因为将非"GPL"的模块插入内核就沾污了内核的"GPL"属性。

1
2
3
4
5
6
module_init(char8139_init);
module_exit(char8139_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ypixunil");
MODULE_DESCRIPTION("Wierd char device driver for Realtek 8139 NIC");

接着往下看char8139_init()

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
static int __init char8139_init(void)
{
	int result;

	PDBG("hello. init.\n");

	/* register our char device */
	result = register_chrdev(char8139_major, "char8139", &char8139_fops);
	if (result < 0) {
		PDBG("Cannot allocate major device number!\n");
		return result;
	}
	/* register_chrdev() will assign a major device number and return if it called
	 * with "major" parameter set to 0 */
	if(char8139_major == 0)
		char8139_major=result;

	/* allocate some kernel memory we need */
	buffer = (unsigned char*)(kmalloc(CHAR8139_BUFFER_SIZE, GFP_KERNEL));
	if (!buffer) {
		PDBG("Cannot allocate memory!\n");
		result = -ENOMEM;
		goto init_fail;
	}
	memset(buffer, 0, CHAR8139_BUFFER_SIZE);
	p_buf = buffer;

	return 0; /* everything's ok */

init_fail:
	char8139_exit();
	return result;
}

这个函数首先的工作就是使用register_chrdev()注册我们的设备的主设备号和系统调用。系统调用对于字符设备驱动程序来说就是file_operations接口。

我们先来看看char8139_major的定义,

1
2
3
4
#define DEFAULT_MAJOR 145         /* data structure used by our driver */
int char8139_major=DEFAULT_MAJOR; /* major device number. if initial value is 0,
				   * the kernel will dynamically assign a major device
				   * number in register_chrdev() */

这里我们指定我们的设备的主设备号是145,你必须找到一个系统中没有用的主设备号,可以通过"cat /proc/devices"命令来查看系统中已经使用的主设备号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[michael@char8139]$ cat /proc/devices
Character devices:
1 mem
2 pty
3 ttyp
4 ttyS
5 cua
7 vcs
10 misc
14 sound
116 alsa
128 ptm
136 pts
162 raw
180 usb
195 nvidia
226 drm

Block devices:
2 fd
3 ide0
22 ide1
[michael@char8139]$

可见在我的系统中,145还没有被使用。

指定主设备号值得考虑。像上面这样指定一个主设备号显然缺乏灵活性,而且不能保证一个驱动程序在所有的机器上都能用。可以在调用register_chrdev()时将第一个参数,即主设备号指定为0,这样register_chrdev()会分配一个空闲的主设备号作为返回值。 但是这样也有问题,我们只有在将模块插入内核之后才能得到我们设备的主设备号(使用 “cat /proc/devices”),但是要操作设备需要在系统/dev目录下建立设备结点,而建立结点时要指定主设备号。当然,你可以写一个脚本来自动完成这些事情。

总之,作为一个演示,我们还是指定主设备号为145,这样我们可以在/dev/目录下建立几个设备节点。

1
2
3
4
[root@char8139]$ mknod /dev/char8139_0 c 145 0
[root@char8139]$ mknod /dev/char8139_0 c 145 17
[root@char8139]$ mknod /dev/char8139_0 c 145 36
[root@char8139]$ mknod /dev/char8139_0 c 145 145

看一下我们建立的节点

1
2
3
4
5
6
[michael@char8139]$ ll /dev/char8139*
crw-r--r-- 1 root root 145, 0 2004-12-26 20:33 /dev/char8139_0
crw-r--r-- 1 root root 145, 17 2004-12-26 20:34 /dev/char8139_1
crw-r--r-- 1 root root 145, 36 2004-12-26 20:34 /dev/char8139_2
crw-r--r-- 1 root root 145, 145 2004-12-26 20:34 /dev/char8139_3
[michael@char8139]$

我们建立了四个节点,使用了四个次设备号,后面我们会说明次设备号的作用。

再来看看我们的file_operations的定义。这里其实只实现了read(),open(),release()三个系统调用,ioctl()只是简单返回。更有write()等函数甚至根本没有声明,没有声明的函数系统可能会调用默认的操作。

1
2
3
4
5
6
7
8
struct file_operations char8139_fops =
{
	owner: THIS_MODULE,
	read: char8139_read,
	ioctl: char8139_ioctl,
	open: char8139_open,
	release: char8139_release,
};

file_operations是每个字符设备驱动程序必须实现的系统调用,当用户对/dev中我们的设备对应结点进行操作时,linux就会调用我们驱动程序中提供的系统调用。比如用户敲入"cat /dev/char8139_0"命令,想想cat这个应用程序的实现,首先它肯定调用C语言库里的open()函数去打开/dev/char8139_0这个文件,到了系统这一层,系统会看到/dev/char8139_0不是普通磁盘文件,而是一个代表字符设备的节点,所以系统会根据/dev/char8139_0的主设备号来查找是不是已经有驱动程序使用这个相同的主设备号进行了注册,如果有,就调用驱动程序的open()实现。

为什么要这样干?因为要提供抽象,提供统一的接口,别忘了操作系统的作用之一就是这个。因为我们的设备提供的统一的接口,所以cat这个应用程序使用一般的文件操作就能从我们的设备中读出数据, 而且more, less这些应用程序都能从我们的设备中读出数据。

现在来看看我们的设备

1
2
3
4
#define CHAR8139_BUFFER_SIZE 2000
unsigned char *buffer=NULL; /* driver data buffer */
unsigned char *p_buf;
unsigned int data_size=0;

我们的设备很简单,一个2000字节的缓冲区, data_size指定缓冲区中有效数据的字节数。我们的设备只支持读不支持写。我们在char8139_init()中为缓冲区分配空间。

char8139_exit()里面的操作就是char8139_init()里面操作的反向操作。

现在我们来看看,假如用户调用了"cat /dev/char8139_3"这个命令会发生什么事情。

根据前面的介绍,我们驱动程序中的open()函数会被调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int char8139_open(struct inode *node, struct file *flip)
{
	int type = MINOR(node->i_rdev)>>4;
	int num = MINOR(node->i_rdev) & 0x0F;

	/* put some char in buffer to reflect the minor device number */
	*buffer=(unsigned char)('0');
	*(buffer+1)=(unsigned char)('x');
	*(buffer+2)=(unsigned char)('0'+type);
	*(buffer+3)=(unsigned char)('0'+num);
	*(buffer+4)=(unsigned char)('\n');
	data_size+=5;

	PDBG("Ok. Find treasure! 8139 I/O port base: %x\n", detect_8139_io_port());
	PDBG("OK. Find treasure! 8139 I/O memory base address: %lx\n",
	detect_8139_io_mem());

	MOD_INC_USE_COUNT;

	return 0;
}

这里演示了次设备号的作用,它让我们知道用户操作的是哪一个"次设备",是/dev/char8139_0还是/dev/char8139_3,因为对不同的"次设备",具体的操作方法可能是不一样的,这样就为一个驱动程序控制多个类似的设备提供了可能。

我们根据次设备号的不同,在buffer中填入不同的字符(次设备号的16进制表示)。

接着驱动程序中的read()函数会被调用,因为cat程序的实现就是读取文件中的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ssize_t char8139_read (struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
	ssize_t ret=0;

	PDBG("copy to user. count=%d, f_pos=%ld\n", (int)count, (long)*f_pos);
	if (*f_pos>= data_size)
		return ret;
	if (*f_pos + count > data_size)
		count = data_size-*f_pos;
	if (copy_to_user(buf, p_buf, count))
	{
		PDBG("OOps, copy to user error.\n");
		return -EFAULT;
	}

	p_buf += count;
	*f_pos += count;
	ret = count;

	return ret;
}

要正确的实现一个read()调用,你得想一想一个应用程序是如何调用read()从文件中读取数据的。如果你想明白了就很简单,驱动程序所要做的就是把恰当的数据传递给应用程序,这是使用copy_to_user()函数完成的。

另外,我们必须得意识到,这里只是一个很简单的演示。还有很多复杂的问题有待考虑,比如两个应用程序可能同时打开我们设备,我们的设备应该怎样反应(这取决于具体的设备应有的行为),还有互斥的问题。

然后我们看看I/O端口和I/O内存的操作。这里使用8139网卡作为一个硬件实例来演示I/O端口和I/O内存的操作。没有什么特别的,都是标准的步骤。在使用时需要注意,如果你的系统中已经有8139网卡的驱动程序,必须先关掉网络设备,卸载驱动,然后再使用本驱动程序。

使用程序包的步骤:(在我的Debian系统上如此,你的可能不同)
1. 解压
2. 编译(/usr/src/linux处必须要有内核源代码)
3. ifconfig eth0 down 关掉网络设备
rmmod 8139too 卸载原来的8139网卡驱动
insmod char8139.o 插入我们的模块
(insmod会出错, 如果你现在运行的linux版本不是你编译本驱动程序时使用的内核源代码的版本,insmod时会报告模块版本与内核版本不一致。这时,你得看看内核源代码中/include/linux/version.h文件,这个文件中的UTS_RELEASE定义了内核的版本号,你可以在驱动程序中预先定义这个宏为当前运行的内核的版本号,这样就能避免上述错误。)
4. mknode(见本文前述)
5. 试试我们的设备
./readtest
或者
cat /dev/char8139_0或
cat /dev/char8139_1或
cat /dev/char8139_2或
cat /dev/char8139_3
6. 恢复系统
rmmod char8139
modprobe 8139too
ifconfig eth0 up
如果你使用dhcp可能还需要运行dhclient

编译android4.4.2源码

android4.4.2和jdk下载 http://pan.baidu.com/share/home?uk=3691037096&view=share#category/type=0

http://www.cnblogs.com/zhx831/p/3550688.html

这篇文章主要记录了我是如何搭建Android编译环境,以及搭建当中遇到的问题以及解决方法。搭建环境依照官网进行,整个搭建环境分为两步:
1. 安装JDK
2. 安装相对应的库以及软件

1、安装JDK

官网上给出的办法是使用apt进行jdk的安装

1
2
3
$ sudo add-apt-repository "deb http://archive.canonical.com/ lucid partner"
$ sudo apt-get update
$ sudo apt-get install sun-java6-jdk

但是我在按照这个方法进行安装是apt提示无法找到jdk安装包。 在更换了好几个source后都无法下载jdk。因此只能考虑手动安装。

1. 首先在Orecal官网下载JDK:

http://www.oracle.com/technetwork/java/javase/downloads/java-archive-downloads-javase6-419409.html

需要注意的是,现在官方网站上最新的版本的JDK7, 但是这个这个版本是不能用于Android的编译的,一定要去下载JDK6. 希望看到的朋友不要在走我的弯路。

2. 创建jvm文件夹
1
sudo mkdir /usr/lib/jvm
3. 安装JDK6
1
2
3
sudo cp jdk-6u45-linux-x64.bin /usr/lib/jvm/
sudo chmod +x jdk-6u45-linux-x64.bin
sudo ./jdk-6u45-linux-x64.bin
4. 设置环境变量
1
2
3
4
5
sudo mv jdk1.6.0_45/ jdk6
export JAVA_HOME=/usr/lib/jvm/jdk6
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH
5. 使能环境变量
1
source ~/.bashrc
6. 设置JDK6为系统默认JDK

因为在ubuntu中默认JDK可能是OpenJDK,这里我们设置JDK6为我们默认的JDK

1
2
3
4
5
sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/jdk6/bin/java 300
sudo update-alternatives --install /usr/bin/javac javac /usr/lib/jvm/jdk6/bin/javac 300
sudo update-alternatives --install /usr/bin/javaws javaws /usr/lib/jvm/jdk6/bin/javaws 300
sudo update-alternatives --install /usr/bin/jar jar /usr/lib/jvm/jdk6/bin/jar 300
sudo update-alternatives --config java
7. 至此JDK就安装好了,现在就可以在shell中查看Java版本
1
java -version

如果编译还提示java错误,就把java路径加到PATH中

1
PATH=$PATH:/usr/lib/jvm/jdk6/bin

2、安装相对应的库以及软件

根据官网上的指示我们需要安装如下软件包和lib

1
2
3
4
5
6
$ sudo apt-get install git gnupg flex bison gperf build-essential \
  zip curl libc6-dev libncurses5-dev:i386 x11proto-core-dev \
  libx11-dev:i386 libreadline6-dev:i386 libgl1-mesa-glx:i386 \
  libgl1-mesa-dev g++-multilib mingw32 tofrodos \
  python-markdown libxml2-utils xsltproc zlib1g-dev:i386
$ sudo ln -s /usr/lib/i386-linux-gnu/mesa/libGL.so.1 /usr/lib/i386-linux-gnu/libGL.so
  • 我安装libgl1-mesa-glx:i386的时候提示要卸掉很多软件,就没装这个,也是能正常编译。

但是在安装过程中遇到了如下错误:

1
2
3
4
Errors were encountered while processing:
libc6-dev:i386
 
E: Sub-process /usr/bin/dpkg returned an error code (1)

解决方法如下:

1
2
$ sudo apt-get install linux-libc-dev:i386
$ sudo apt-get install libc6-dev:i386

http://blog.csdn.net/yf210yf/article/details/9206269

http://www.cnblogs.com/qianxudetianxia/p/3681890.html

编译代码

进入源码根目录,编译初始化,在终端中执行:

1
source build/envsetup.sh

选择编译目标:

1
lunch 

选择第一个吧,或者直接

1
lunch aosp_arm-eng

开始编译

1
make -j4

3、5个小时左右吧

Android 完成编译的时候先执行 source build/envsetup.sh。 在这个shell 脚本中定义了 help, croot, m, mm, mmm 等 function

运行模拟器

在终端中执行:emulator

修改和编译系统应用代码

我们修改一下系统应用Mms的标题:

使用mm命令编译:

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
xxx@ubuntu:~/Data/android/packages/apps/Mms$ mm
============================================
PLATFORM_VERSION_CODENAME=REL
PLATFORM_VERSION=4.4.3.2.1.000.000
TARGET_PRODUCT=aosp_arm
TARGET_BUILD_VARIANT=eng
TARGET_BUILD_TYPE=release
TARGET_BUILD_APPS=
TARGET_ARCH=arm
TARGET_2ND_ARCH=
TARGET_ARCH_VARIANT=armv7-a
TARGET_CPU_VARIANT=generic
HOST_ARCH=x86
HOST_OS=linux
HOST_OS_EXTRA=Linux-3.13.0-24-generic-x86_64-with-Ubuntu-14.04-trusty
HOST_BUILD_TYPE=release
BUILD_ID=OPENMASTER
OUT_DIR=out
============================================
make:进入目录'/home/xxx/Data/android'
target R.java/Manifest.java: Mms (out/target/common/obj/APPS/Mms_intermediates/src/R.stamp)
warning: string 'menu_insert_smiley' has no default translation.
target Java: Mms (out/target/common/obj/APPS/Mms_intermediates/classes)
注: 某些输入文件使用或覆盖了已过时的 API。
注: 有关详细信息, 请使用 -Xlint:deprecation 重新编译。
注: 某些输入文件使用了未经检查或不安全的操作。
注: 有关详细信息, 请使用 -Xlint:unchecked 重新编译。
Copying: out/target/common/obj/APPS/Mms_intermediates/classes-jarjar.jar
Copying: out/target/common/obj/APPS/Mms_intermediates/emma_out/lib/classes-jarjar.jar
Copying: out/target/common/obj/APPS/Mms_intermediates/classes.jar
Proguard: out/target/common/obj/APPS/Mms_intermediates/proguard.classes.jar
ProGuard, version 4.10
Reading program jar [/home/xxx/Data/android/out/target/common/obj/APPS/Mms_intermediates/classes.jar]
// ... ...
target Dex: Mms
Copying: out/target/common/obj/APPS/Mms_intermediates/classes.dex
target Package: Mms (out/target/product/generic/obj/APPS/Mms_intermediates/package.apk)
nothing matches overlay file ic_contact_picture.png, for flavor ,,,,,,,,,,,,mdpi,,,,,,,
libpng warning: iCCP: known incorrect sRGB profile
libpng warning: iCCP: known incorrect sRGB profile
warning: string 'menu_insert_smiley' has no default translation.
warning: string 'menu_insert_smiley' is missing 19 required localizations: az_AZ en_AU en_CA en_GB en_IN en_NZ en_SG en_US eo_EU hy_AM ka_GE km_KH lo_LA mn_MN ne_NP si_LK zh_CN zh_HK zh_TW
dex2oatd I 21424 21424 art/dex2oat/dex2oat.cc:1082] dex2oat: out/host/linux-x86/bin/dex2oatd --runtime-arg -Xms64m --runtime-arg -Xmx64m --boot-image=out/target/product/generic/dex_bootjars/system/framework/boot.art --dex-file=out/target/product/generic/obj/APPS/Mms_intermediates/package.apk --dex-location=/system/priv-app/Mms.apk --oat-file=out/target/product/generic/obj/APPS/Mms_intermediates/package.odex --android-root=out/target/product/generic/system --instruction-set=arm --instruction-set-features=default
dex2oatd I 21424 21424 art/dex2oat/dex2oat.cc:252] dex2oat took 1.189425041s (threads: 8)
Notice file: packages/apps/Mms/NOTICE -- out/target/product/generic/obj/NOTICE_FILES/src//system/priv-app/Mms.apk.txt
Install: out/target/product/generic/system/priv-app/Mms.apk
Install: out/target/product/generic/system/priv-app/Mms.odex
// ... ...
Install: out/target/product/generic/data/app/MmsTests.apk
Install: out/target/product/generic/data/app/MmsTests.odex
make:离开目录“/home/xxx/Data/android”

主要是生成了两个文件:
out/target/product/generic/system/priv-app/Mms.apk
out/target/product/generic/system/priv-app/Mms.odex

安装到手机

因为是系统应用,为了重启有效,使用push命令把两个文件push到模拟器中对应的位置:

1
2
3
4
5
6
// 注意现在一些核心的应用的位置由以前的system/app调整为了system/priv-app,不要push错了
/*
xxx@ubuntu:~/Data/android$ adb push out/target/product/generic/system/priv-app/Mms.odex /system/priv-app
2893 KB/s (2085348 bytes in 0.703s)
xxx@ubuntu:~/Data/android$ adb push out/target/product/generic/system/priv-app/Mms.apk /system/priv-app
3315 KB/s (1785258 bytes in 0.525s)

看模拟器的标题被替换了。类似了,你可以修改framework,替换内核

在 ~/.bashrc

添加环境变量:

1
2
export ANDROID_PRODUCT_OUT=~/code/android-4.2/out/target/product/generic
export ANDROID_SWT=~/code/android-4.2/out/host/linux-x86/framework/

备注:

执行emulator,出现如下错误:

1
2
3
4
5
6
emulator: ERROR: You did not specify a virtual device name, and the system
directory could not be found.

If you are an Android SDK user, please use '@<name>' or '-avd <name>'
to start a given virtual device (see -help-avd for details).
Otherwise, follow the instructions in -help-disk-images to start the emulator

解决:

1
2
source build/envsetup.sh 
lunch sdk-eng

然后再执行

1
emulator

可以启动模拟器


http://senrsl.blogspot.com/2015/03/s4shv-e300lkernelrom.html

三星S4韩版SHV-E300L源码编译(内核Kernel+ROM)

编译ROM

①准备工作

先看说明README_Platform.txt

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
    How to build Module for Platform
    - It is only for modules are needed to using Android build system.
    - Please check its own install information under its folder for other module.
    [Step to build]
    1. Get android open source.
        : version info - Android 4.4
        ( Download site : http://source.android.com )
    2. Copy module that you want to build - to original android open source
       If same module exist in android open source, you should replace it. (no overwrite)
        # It is possible to build all modules at once.
    3. You should add module name to 'PRODUCT_PACKAGES' in 'build\target\product\core.mk' as following case.
        case 1) e2fsprog : should add 'e2fsck' to PRODUCT_PACKAGES
        case 2) blkid : should add 'libkeyutils' to PRODUCT_PACKAGES
        case 3) libhyphenation : should add 'libkeyutils' to PRODUCT_PACKAGES
        case 4) KeyUtils : should add 'libkeyutils' to PRODUCT_PACKAGES
        case 5) libexifa : should add 'libexifa' to PRODUCT_PACKAGES
        case 6) libjpega : should add 'libjpega' to PRODUCT_PACKAGES
        ex.) [build\target\product\core.mk] - add all module name for case 1 ~ 5 at once
            PRODUCT_PACKAGES += \
                e2fsck \
                blkid \
                libhyphenation \
                libkeyutils \
                libexifa \
                libjpega \
                ebtables
    4. excute build command
        ./build_platform.sh
    Note :
    to build SBrowser (vendor/samsung/packages/apps/SBrowser),
    please refer to Buildme.txt at the folder mentioned above.

意思就是说分4部走,后面加了个注释。

分别是下载4.4的源码,把包里的要编译的模块拷进去(替换,非覆盖),增加要编译的模块到core.mk进行注册,执行编译脚本。

查看当前源码版本

1
2
3
4
5
6
senrsl@senrsl-ubuntu:~$ cd android/source/WORKING_DIRECTORY/
senrsl@senrsl-ubuntu:~/android/source/WORKING_DIRECTORY$ repo branches
*  android-4.4.2_r2          | in all projects
    master                    | in:
                                      abi/cpp
                                      ....

所有的版本号里没有叫4.4的,只有4.4.*的。。。。

好吧,就用这个。

②替换模块

1)external目录,把这些目录剪出来

1
2
3
4
5
6
7
8
9
10
11
12
    senrsl@senrsl-ubuntu:~/android/source/test/三星替换 /external$ ll
    总用量 40
    drwxrwxr-x 10 senrsl senrsl 4096  3月 30 14:53 ./
    drwxrwxr-x  4 senrsl senrsl 4096  3月 30 15:02 ../
    drwxrwxr-x 17 senrsl senrsl 4096  3月 20 13:35 chromium/
    drwxrwxr-x  9 senrsl senrsl 4096  3月 20 13:36 dnsmasq/
    drwxrwxr-x 19 senrsl senrsl 4096  3月 20 13:36 e2fsprogs/
    drwxrwxr-x  3 senrsl senrsl 4096  3月 20 13:36 gcc-demangle/
    drwxrwxr-x  4 senrsl senrsl 4096  3月 20 13:36 hyphenation/
    drwxrwxr-x 15 senrsl senrsl 4096  3月 20 13:36 iproute2/
    drwxrwxr-x 11 senrsl senrsl 4096  3月 20 13:36 iptables/
    drwxrwxr-x  4 senrsl senrsl 4096  3月 20 13:36 junit/

把这些目录放进去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    senrsl@senrsl-ubuntu:~$ cd android/source/SHV-E300L_KK_Opensource/Platform/external/
    senrsl@senrsl-ubuntu:~/android/source/SHV-E300L_KK_Opensource/Platform/external$ ll
    总用量 44
    drwxrwxr-x 11 senrsl senrsl 4096 10月 14 09:23 ./
    drwxrwxr-x  5 senrsl senrsl 4096  3月 30 13:10 ../
    drwxrwxr-x 17 senrsl senrsl 4096 10月  8 12:16 chromium/
    drwxrwxr-x  9 senrsl senrsl 4096 10月  8 09:07 dnsmasq/
    drwxrwxr-x 18 senrsl senrsl 4096 10月  8 09:07 e2fsprogs/
    drwxrwxr-x  2 senrsl senrsl 4096 10月  8 09:07 gcc-demangle/
    drwxrwxr-x  3 senrsl senrsl 4096 10月  8 09:07 hyphenation/
    drwxrwxr-x 14 senrsl senrsl 4096 10月  8 09:07 iproute2/
    drwxrwxr-x 10 senrsl senrsl 4096 10月  8 09:07 iptables/
    drwxrwxr-x  3 senrsl senrsl 4096 10月  8 09:07 junit/
    drwxrwxr-x 10 senrsl senrsl 4096 10月  8 12:23 webkit/

2)vendor目录,把vendor/samsung放进去

3)build目录,把这俩文件剪出来,把Platform里的放进去

1
2
3
4
5
6
7
8
9
    senrsl@senrsl-ubuntu:~/android/source/SHV-E300L_KK_Opensource/Platform/build$ tree
    .
    └── target
        ├── board
        │   └── generic
        │       └── BoardConfig.mk
        └── product
            └── core.mk
    4 directories, 2 files
③执行编译
1
    senrsl@senrsl-ubuntu:~/android/source/WORKING_DIRECTORY$ ./build_platform.sh

报错

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
    target Generated: libwebcore <= external/webkit/Source/WebCore/dom/make_names.pl
    target Generated: libwebcore <= external/webkit/Source/WebCore/dom/make_names.pl
    target Generated: libwebcore <= external/webkit/Source/WebCore/dom/make_names.pl
    target Generated: libwebcore <= external/webkit/Source/WebCore/dom/make_names.pl
    target Generated: libwebcore <= external/webkit/Source/WebCore/dom/make_names.pl
    target Generated: libwebcore <= external/webkit/Source/WebCore/dom/make_names.pl
    target Generated: libwebcore <= external/webkit/Source/WebCore/dom/make_names.pl
    target Generated: libwebcore <= external/webkit/Source/WebCore/dom/make_names.pl
    target Generated: libwebcore <= external/webkit/Source/WebCore/dom/make_names.pl
    Export includes file: frameworks/opt/emoji/Android.mk -- out/target/product/generic/obj/SHARED_LIBRARIES/libemoji_intermediates/export_includes
    Export includes file: external/harfbuzz/Android.mk -- out/target/product/generic/obj/SHARED_LIBRARIES/libharfbuzz_intermediates/export_includes
    Export includes file: external/webkit/Android.mk -- out/target/product/generic/obj/STATIC_LIBRARIES/libwebcore_intermediates/export_includes
    Export includes file: external/libxml2/Android.mk -- out/target/product/generic/obj/STATIC_LIBRARIES/libxml2_intermediates/export_includes
    Export includes file: external/libxslt/Android.mk -- out/target/product/generic/obj/STATIC_LIBRARIES/libxslt_intermediates/export_includes
    Export includes file: external/hyphenation/Android.mk -- out/target/product/generic/obj/STATIC_LIBRARIES/libhyphenation_intermediates/export_includes
    Import includes file: out/target/product/generic/obj/SHARED_LIBRARIES/libemoji_intermediates/import_includes
    Import includes file: out/target/product/generic/obj/SHARED_LIBRARIES/libharfbuzz_intermediates/import_includes
    target Generated: libwebcore <= external/webkit/Source/WebCore/html/DocTypeStrings.gperf
    Generating HTMLEntityTable.cpp
    target Generated: libwebcore <= external/webkit/Source/WebCore/platform/ColorData.gperf
    WebCore Yacc: libwebcore <= external/webkit/Source/WebCore/css/CSSGrammar.y
    target Generated: libwebcore <= external/webkit/Source/WebCore/dom/make_names.pl
    WebCore Yacc: libwebcore <= external/webkit/Source/WebCore/xml/XPathGrammar.y
    target Generated: libwebcore <= external/webkit/Source/WebCore/dom/make_names.pl
    Can't locate Switch.pm in @INC (you may need to install the Switch module) (@INC contains: /etc/perl /usr/local/lib/perl/5.18.2 /usr/local/share/perl/5.18.2 /usr/lib/perl5 /usr/share/perl5 /usr/lib/perl/5.18 /usr/share/perl/5.18 /usr/local/lib/site_perl .) at external/webkit/Source/WebCore/make-hash-tools.pl line 23.
    BEGIN failed--compilation aborted at external/webkit/Source/WebCore/make-hash-tools.pl line 23.
    Can't locate Switch.pm in @INC (you may need to install the Switch module) (@INC contains: /etc/perl /usr/local/lib/perl/5.18.2 /usr/local/share/perl/5.18.2 /usr/lib/perl5 /usr/share/perl5 /usr/lib/perl/5.18 /usr/share/perl/5.18 /usr/local/lib/site_perl .) at external/webkit/Source/WebCore/make-hash-tools.pl line 23.
    BEGIN failed--compilation aborted at external/webkit/Source/WebCore/make-hash-tools.pl line 23.
    make: *** [out/target/product/generic/obj/STATIC_LIBRARIES/libwebcore_intermediates/Source/WebCore/html/DocTypeStrings.cpp] 错误 2
    make: *** 正在等待未完成的任务....
    make: *** [out/target/product/generic/obj/STATIC_LIBRARIES/libwebcore_intermediates/Source/WebCore/platform/ColorData.cpp] 错误 2
    target Generated: libwebcore <= external/webkit/Source/WebCore/html/parser/HTMLEntityNames.in
    senrsl@senrsl-ubuntu:~/android/source/WORKING_DIRECTORY$ ./build_platform.sh

在core.mk里把 libwebcore \删掉,再build,报错

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
    Install: out/target/product/generic/system/fonts/NotoSansKhmerUI-Bold.ttf
    Install: out/target/product/generic/system/fonts/NotoSansKhmerUI-Regular.ttf
    Install: out/target/product/generic/system/fonts/NotoSansLao-Bold.ttf
    Install: out/target/product/generic/system/fonts/NotoSansLao-Regular.ttf
    Install: out/target/product/generic/system/fonts/NotoSansLaoUI-Bold.ttf
    Install: out/target/product/generic/system/fonts/NotoSansLaoUI-Regular.ttf
    Install: out/target/product/generic/system/fonts/NotoSansMalayalam-Bold.ttf
    collect2: error: ld returned 1 exit status
    Install: out/target/product/generic/system/fonts/NotoSansMalayalam-Regular.ttf
    make: *** [out/target/product/generic/obj/EXECUTABLES/dnsmasq_intermediates/LINKED/dnsmasq] 错误 1
    make: *** 正在等待未完成的任务....
    Install: out/target/product/generic/system/fonts/NotoSansMalayalamUI-Bold.ttf
    external/e2fsprogs/lib/ext2fs/blkmap64_ba.c:339: error: undefined reference to 'ext2fs_test_bit64'
    external/e2fsprogs/lib/ext2fs/blkmap64_ba.c:390: error: undefined reference to 'ext2fs_test_bit64'
    external/e2fsprogs/lib/ext2fs/blkmap64_ba.c:186: error: undefined reference to 'ext2fs_test_bit64'
    external/e2fsprogs/lib/ext2fs/blkmap64_ba.c:281: error: undefined reference to 'ext2fs_mem_is_zero'
    external/e2fsprogs/lib/ext2fs/blkmap64_ba.c:178: error: undefined reference to 'ext2fs_clear_bit64'
    external/e2fsprogs/lib/ext2fs/blkmap64_ba.c:170: error: undefined reference to 'ext2fs_set_bit64'
    external/e2fsprogs/lib/ext2fs/blkmap64_ba.c:141: error: undefined reference to 'ext2fs_clear_bit64'
    external/e2fsprogs/lib/ext2fs/gen_bitmap64.c:735: error: undefined reference to 'ext2fs_get_bitmap_granularity'
    external/e2fsprogs/lib/ext2fs/gen_bitmap64.c:790: error: undefined reference to 'ext2fs_find_first_zero_generic_bitmap'
    external/e2fsprogs/lib/ext2fs/mmp.c:57: error: undefined reference to 'ext2fs_get_dio_alignment'
    external/e2fsprogs/lib/ext2fs/mmp.c:213: error: undefined reference to 'ext2fs_alloc_block2'
    collect2: error: ld returned 1 exit status
    make: *** [out/target/product/generic/obj/SHARED_LIBRARIES/libext2fs_intermediates/LINKED/libext2fs.so] 错误 1
    senrsl@senrsl-ubuntu:~/android/source/WORKING_DIRECTORY$

再把 libexifa \删掉,报错

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
    out/host/linux-x86/obj/SHARED_LIBRARIES/libext2fs_host_intermediates/blkmap64_ba.o: In function `ba_find_first_zero':
    /home/senrsl/android/source/WORKING_DIRECTORY/external/e2fsprogs/lib/ext2fs/blkmap64_ba.c:339: undefined reference to `ext2fs_test_bit64'
    /home/senrsl/android/source/WORKING_DIRECTORY/external/e2fsprogs/lib/ext2fs/blkmap64_ba.c:390: undefined reference to `ext2fs_test_bit64'
    out/host/linux-x86/obj/SHARED_LIBRARIES/libext2fs_host_intermediates/blkmap64_ba.o: In function `ba_test_bmap':
    /home/senrsl/android/source/WORKING_DIRECTORY/external/e2fsprogs/lib/ext2fs/blkmap64_ba.c:186: undefined reference to `ext2fs_test_bit64'
    out/host/linux-x86/obj/SHARED_LIBRARIES/libext2fs_host_intermediates/blkmap64_ba.o: In function `ba_test_clear_bmap_extent':
    /home/senrsl/android/source/WORKING_DIRECTORY/external/e2fsprogs/lib/ext2fs/blkmap64_ba.c:281: undefined reference to `ext2fs_mem_is_zero'
    out/host/linux-x86/obj/SHARED_LIBRARIES/libext2fs_host_intermediates/blkmap64_ba.o: In function `ba_unmark_bmap':
    /home/senrsl/android/source/WORKING_DIRECTORY/external/e2fsprogs/lib/ext2fs/blkmap64_ba.c:178: undefined reference to `ext2fs_clear_bit64'
    out/host/linux-x86/obj/SHARED_LIBRARIES/libext2fs_host_intermediates/blkmap64_ba.o: In function `ba_mark_bmap':
    /home/senrsl/android/source/WORKING_DIRECTORY/external/e2fsprogs/lib/ext2fs/blkmap64_ba.c:170: undefined reference to `ext2fs_set_bit64'
    out/host/linux-x86/obj/SHARED_LIBRARIES/libext2fs_host_intermediates/blkmap64_ba.o: In function `ba_resize_bmap':
    /home/senrsl/android/source/WORKING_DIRECTORY/external/e2fsprogs/lib/ext2fs/blkmap64_ba.c:141: undefined reference to `ext2fs_clear_bit64'
    out/host/linux-x86/obj/SHARED_LIBRARIES/libext2fs_host_intermediates/gen_bitmap64.o: In function `ext2fs_convert_subcluster_bitmap':
    /home/senrsl/android/source/WORKING_DIRECTORY/external/e2fsprogs/lib/ext2fs/gen_bitmap64.c:735: undefined reference to `ext2fs_get_bitmap_granularity'
    out/host/linux-x86/obj/SHARED_LIBRARIES/libext2fs_host_intermediates/gen_bitmap64.o: In function `ext2fs_find_first_zero_generic_bmap':
    /home/senrsl/android/source/WORKING_DIRECTORY/external/e2fsprogs/lib/ext2fs/gen_bitmap64.c:790: undefined reference to `ext2fs_find_first_zero_generic_bitmap'
    out/host/linux-x86/obj/SHARED_LIBRARIES/libext2fs_host_intermediates/mmp.o: In function `ext2fs_mmp_read':
    /home/senrsl/android/source/WORKING_DIRECTORY/external/e2fsprogs/lib/ext2fs/mmp.c:57: undefined reference to `ext2fs_get_dio_alignment'
    out/host/linux-x86/obj/SHARED_LIBRARIES/libext2fs_host_intermediates/mmp.o: In function `ext2fs_mmp_init':
    /home/senrsl/android/source/WORKING_DIRECTORY/external/e2fsprogs/lib/ext2fs/mmp.c:213: undefined reference to `ext2fs_alloc_block2'
    collect2: ld returned 1 exit status
    make: *** [out/host/linux-x86/obj/lib/libext2fs_host.so] 错误 1
    make: *** 正在等待未完成的任务....
    1 warning generated.
    external/openssl/ssl/s2_clnt.c:1027:38: warning: unused parameter 'type' [-Wunused-parameter]
    int ssl2_set_certificate(SSL *s, int type, int len, const unsigned char *data)
                                         ^
    1 warning generated.
    external/openssl/ssl/s2_lib.c:385:38: warning: unused parameter 'larg' [-Wunused-parameter]
    long ssl2_ctrl(SSL *s, int cmd, long larg, void *parg)
                                         ^
    external/openssl/ssl/s2_lib.c:385:50: warning: unused parameter 'parg' [-Wunused-parameter]
    long ssl2_ctrl(SSL *s, int cmd, long larg, void *parg)
                                                     ^
    external/openssl/ssl/s2_lib.c:400:30: warning: unused parameter 's' [-Wunused-parameter]
    long ssl2_callback_ctrl(SSL *s, int cmd, void (*fp)(void))
                                 ^
    external/openssl/ssl/s2_lib.c:400:37: warning: unused parameter 'cmd' [-Wunused-parameter]
    long ssl2_callback_ctrl(SSL *s, int cmd, void (*fp)(void))
                                        ^
    external/openssl/ssl/s2_lib.c:400:49: warning: unused parameter 'fp' [-Wunused-parameter]
    long ssl2_callback_ctrl(SSL *s, int cmd, void (*fp)(void))
                                                    ^
    external/openssl/ssl/s2_lib.c:405:29: warning: unused parameter 'ctx' [-Wunused-parameter]
    long ssl2_ctx_ctrl(SSL_CTX *ctx, int cmd, long larg, void *parg)
                                ^
    external/openssl/ssl/s2_lib.c:405:38: warning: unused parameter 'cmd' [-Wunused-parameter]
    long ssl2_ctx_ctrl(SSL_CTX *ctx, int cmd, long larg, void *parg)
                                         ^
    external/openssl/ssl/s2_lib.c:405:48: warning: unused parameter 'larg' [-Wunused-parameter]
    long ssl2_ctx_ctrl(SSL_CTX *ctx, int cmd, long larg, void *parg)
                                                   ^
    external/openssl/ssl/s2_lib.c:405:60: warning: unused parameter 'parg' [-Wunused-parameter]
    long ssl2_ctx_ctrl(SSL_CTX *ctx, int cmd, long larg, void *parg)
                                                               ^
    external/openssl/ssl/s2_lib.c:410:38: warning: unused parameter 'ctx' [-Wunused-parameter]
    long ssl2_ctx_callback_ctrl(SSL_CTX *ctx, int cmd, void (*fp)(void))
                                         ^
    external/openssl/ssl/s2_lib.c:410:47: warning: unused parameter 'cmd' [-Wunused-parameter]
    long ssl2_ctx_callback_ctrl(SSL_CTX *ctx, int cmd, void (*fp)(void))
                                                  ^
    external/openssl/ssl/s2_lib.c:410:59: warning: unused parameter 'fp' [-Wunused-parameter]
    long ssl2_ctx_callback_ctrl(SSL_CTX *ctx, int cmd, void (*fp)(void))
                                                              ^
    12 warnings generated.
    1 warning generated.
    make: *** wait: 没有子进程。 停止。
    senrsl@senrsl-ubuntu:~/android/source/WORKING_DIRECTORY$

然后把之前的core.mk第二部分替换成README里的,报错

1
2
3
4
5
6
7
8
    /home/senrsl/android/source/WORKING_DIRECTORY/external/e2fsprogs/lib/ext2fs/mmp.c:57: undefined reference to `ext2fs_get_dio_alignment'
    out/host/linux-x86/obj/SHARED_LIBRARIES/libext2fs_host_intermediates/mmp.o: In function `ext2fs_mmp_init':
    /home/senrsl/android/source/WORKING_DIRECTORY/external/e2fsprogs/lib/ext2fs/mmp.c:213: undefined reference to `ext2fs_alloc_block2'
    collect2: ld returned 1 exit status
    make: *** [out/host/linux-x86/obj/lib/libext2fs_host.so] 错误 1
    make: *** 正在等待未完成的任务....
    Processing target/product/generic/dex_bootjars/system/framework/core.jar
    Done!

然后把core.mk里第二部分全都删掉,报的错还是上面那个。

恢复下core.mk重来,

报1:报错external/webkit/Source/WebCore/make-hash-tools.pl line 23.,core.mk砍掉 libwebcore \ 代码砍掉external/webkit.

报2:/home/senrsl/android/source/WORKING_DIRECTORY/external/e2fsprogs/lib/ext2fs/mmp.c:213: undefined reference to `ext2fs_alloc_block2',external/e2fsprogs 代码换回原版。

报3:/home/senrsl/android/source/WORKING_DIRECTORY/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7/bin/../lib/gcc/arm-linux-androideabi/4.7/../../../../arm-linux-androideabi/bin/ld: error: out/target/product/generic/obj/EXECUTABLES/dnsmasq_intermediates/dhcp-common.o: multiple definition of ‘option_string’ /home/senrsl/android/source/WORKING_DIRECTORY/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7/bin/../lib/gcc/arm-linux-androideabi/4.7/../../../../arm-linux-androideabi/bin/ld: out/target/product/generic/obj/EXECUTABLES/dnsmasq_intermediates/option.o: previous definition here,external/dnsmasq换回原版。

然后编译

这样竟编译成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    Creating filesystem with parameters:
        Size: 576716800
        Block size: 4096
        Blocks per group: 32768
        Inodes per group: 7040
        Inode size: 256
        Journal blocks: 2200
        Label:
        Blocks: 140800
        Block groups: 5
        Reserved block group size: 39
    Created filesystem with 892/35200 inodes and 65272/140800 blocks
    + '[' 0 -ne 0 ']'
    Install system fs image: out/target/product/generic/system.img
    out/target/product/generic/system.img+ maxsize=588791808 blocksize=2112 total=576716800 reserve=5947392

镜像文件输出到了/home/senrsl/android/source/WORKING_DIRECTORY/out/target /product/generic

4)封包

查看下官方提供的E300LKLUFNE4_E300LLGTFNE4_E300LKLUFNC1_HOME.tar

依次罗列了这几个img.

尝试封装

1
2
3
4
5
    senrsl@senrsl-ubuntu:~/android/source/WORKING_DIRECTORY/out/target/product/generic$ tar cvf p.tar cache.img ramdisk.img system.img userdata.img
    cache.img
    ramdisk.img
    system.img
    userdata.img

然后重启系统去烧。

//TODO 未成功,无法烧入


I9507V 的external/e2fsprogs,external/dnsmasq换回原版,就能编译成功,没试过烧进真机。

TCP的URG标志和内核实现

TCP的URG标志和内核实现之一:协议
TCP的URG标志和内核实现之二:发送的实现
TCP的URG标志和内核实现之三:接收的实现


TCP的URG标志和内核实现之一:协议

定义urgent数据的目的:
urgent机制,是用于通知应用层需要接收urgent data,在urgent data接收完成后,通知应用层urgent data数据接收完毕。相关协议文本RFC793 RFC1122 RFC6093

哪些数据是urgent data?

协议规定

在TCP报头的URG位有效的时候,通过TCP报头中的urgent pointer来标识urgent data的位置,但是在urgent pointer的解析方式上各个协议文本的描述有差异:

解读一:RFC793 P17,描述是“The urgent pointer points to the sequence number of the octet following the urgent data.”,在P41有描述“This mechanism permits a point in the data stream to be designated as the end of urgent information. Whenever this point is in advance of the receive sequence number (RCV.NXT) at the receiving TCP, that TCP must tell the user to go into “urgent mode”; when the receive sequence number catches up to the urgent pointer, the TCP must tell user to go”,可以认为是:当前接收的报文中SEQ在SEG.SEQ+Urgent Pointer之前的都是,而urgent pointer是第一个非urgent data( TCP已经接受,但是还没有提交给应用的数据是不是呢?)

解读二:在P56的描述是“If the urgent flag is set, then SND.UP <-SND.NXT-1 and set the urgent pointer in the outgoing segments”,也就是urgent pointer是最后一个urgent data字节。而在RFC1122中消除了这一歧义:在P84中说明“the urgent pointer points to the sequence number of the LAST octet (not LAST+1) in a sequence of urgent data”

linux实现

虽然在RFC1122中消除了这一歧义,linux仍然使用了解读一的解析方式,如果要使用解读二定义的方式,需要使用tcp_stdurg这个配置项。

urgent data数据能有多长?

协议规定

按照RFC793 P41的描述,长度不受限,RFC1122 P84中,更是明确了“A TCP MUST support a sequence of urgent data of any length”

linux实现

其实,linux只支持1BYTE的urgent data

urgent data与OOB数据

OOB数据说的是带外数据,也就是这些数据不是放到TCP流供读取的,而是通过额外的接口来获取,linux默认把urgent data实现为OOB数据;而按照协议的规定,urgent data不是out of band data

由于OOB数据的协议和实现上存在很多不确定因素,因此现在已经不建议使用了


TCP的URG标志和内核实现之二:发送的实现

Linxu内核在默认情况下,把urgent data实现为OOB数据

发送URG数据的接口

在内核态,使用kernel_sendmsg/kernel_sendpage完成发送,只不过需要加上MSG_OOB标志,表示要发送的URG数据。

URG数据发送接口的实现

分片主要在kernel_sendmsg中完成,在OOB数据的处理上,它和kernel_sendpage是一致

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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
int tcp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,  
		size_t size)  
{  
	。。。。。。。。。。。。。。  
	/*如果flags设置了MSG_OOB该接口其实返回的mss_now关闭了TSO功能*/  
	mss_now = tcp_send_mss(sk, &size_goal, flags);  
	。。。。。。。。。。。。。。  
	while (--iovlen >= 0) {  
		size_t seglen = iov->iov_len;  
		unsigned char __user *from = iov->iov_base;  

		iov++;  

		while (seglen > 0) {  
			int copy = 0;  
			int max = size_goal;  

			skb = tcp_write_queue_tail(sk);  
			if (tcp_send_head(sk)) {  
				if (skb->ip_summed == CHECKSUM_NONE)  
					max = mss_now;  
				copy = max - skb->len;  
			}  

			if (copy <= 0) {  
new_segment:  
				/* Allocate new segment. If the interface is SG, 
				 * allocate skb fitting to single page. 
				 */  
				if (!sk_stream_memory_free(sk))  
					goto wait_for_sndbuf;  

				skb = sk_stream_alloc_skb(sk,  
							  select_size(sk, sg),  
							  sk->sk_allocation);  
				if (!skb)  
					goto wait_for_memory;  

				/* 
				 * Check whether we can use HW checksum. 
				 */  
				if (sk->sk_route_caps & NETIF_F_ALL_CSUM)  
					skb->ip_summed = CHECKSUM_PARTIAL;  

				skb_entail(sk, skb);  
				copy = size_goal;  
				max = size_goal;  
			}  

			/* Try to append data to the end of skb. */  
			if (copy > seglen)  
				copy = seglen;  

			/* Where to copy to? */  
			if (skb_availroom(skb) > 0) {  
				/* We have some space in skb head. Superb! */  
				copy = min_t(int, copy, skb_availroom(skb));  
				err = skb_add_data_nocache(sk, skb, from, copy);  
				if (err)  
					goto do_fault;  
			} else {  
				int merge = 0;  
				int i = skb_shinfo(skb)->nr_frags;  
				struct page *page = sk->sk_sndmsg_page;  
				int off;  

				if (page && page_count(page) == 1)  
					sk->sk_sndmsg_off = 0;  

				off = sk->sk_sndmsg_off;  

				if (skb_can_coalesce(skb, i, page, off) &&  
					off != PAGE_SIZE) {  
					/* We can extend the last page 
					 * fragment. */  
					merge = 1;  
				} else if (i == MAX_SKB_FRAGS || !sg) {  
					/* Need to add new fragment and cannot 
					 * do this because interface is non-SG, 
					 * or because all the page slots are 
					 * busy. */  
					tcp_mark_push(tp, skb);  
					goto new_segment;  
				} else if (page) {  
					if (off == PAGE_SIZE) {  
						put_page(page);  
						sk->sk_sndmsg_page = page = NULL;  
						off = 0;  
					}  
				} else  
					off = 0;  

				if (copy > PAGE_SIZE - off)  
					copy = PAGE_SIZE - off;  
				if (!sk_wmem_schedule(sk, copy))  
					goto wait_for_memory;  

				if (!page) {  
					/* Allocate new cache page. */  
					if (!(page = sk_stream_alloc_page(sk)))  
						goto wait_for_memory;  
				}  

				/* Time to copy data. We are close to 
				 * the end! */  
				err = skb_copy_to_page_nocache(sk, from, skb,  
								   page, off, copy);  
				if (err) {  
					/* If this page was new, give it to the 
					 * socket so it does not get leaked. 
					 */  
					if (!sk->sk_sndmsg_page) {  
						sk->sk_sndmsg_page = page;  
						sk->sk_sndmsg_off = 0;  
					}  
					goto do_error;  
				}  

				/* Update the skb. */  
				if (merge) {  
					skb_frag_size_add(&skb_shinfo(skb)->frags[i - 1], copy);  
				} else {  
					skb_fill_page_desc(skb, i, page, off, copy);  
					if (sk->sk_sndmsg_page) {  
						get_page(page);  
					} else if (off + copy < PAGE_SIZE) {  
						get_page(page);  
						sk->sk_sndmsg_page = page;  
					}  
				}  

				sk->sk_sndmsg_off = off + copy;  
			}  

			if (!copied)  
				TCP_SKB_CB(skb)->tcp_flags &= ~TCPHDR_PSH;  

			tp->write_seq += copy;  
			TCP_SKB_CB(skb)->end_seq += copy;  
			skb_shinfo(skb)->gso_segs = 0;  

			from += copy;  
			copied += copy;  
			if ((seglen -= copy) == 0 && iovlen == 0)  
				goto out;  
			/*对于OOB数据,即使一个分片用光,如果还有 
			send_buff和OOB数据,就继续积累分片*/  
			if (skb->len < max || (flags & MSG_OOB))  
				continue;  

			if (forced_push(tp)) {  
				tcp_mark_push(tp, skb);  
				__tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_PUSH);  
			} else if (skb == tcp_send_head(sk))  
				tcp_push_one(sk, mss_now);  
			continue;  

wait_for_sndbuf:  
			set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);  
wait_for_memory:  
			if (copied)  
				tcp_push(sk, flags & ~MSG_MORE, mss_now, TCP_NAGLE_PUSH);  

			if ((err = sk_stream_wait_memory(sk, &timeo)) != 0)  
				goto do_error;  

			mss_now = tcp_send_mss(sk, &size_goal, flags);  
		}  
	}  

out:  
	if (copied)  
		tcp_push(sk, flags, mss_now, tp->nonagle);  
	release_sock(sk);  
	return copied;  

do_fault:  
	if (!skb->len) {  
		tcp_unlink_write_queue(skb, sk);  
		/* It is the one place in all of TCP, except connection 
		 * reset, where we can be unlinking the send_head. 
		 */  
		tcp_check_send_head(sk, skb);  
		sk_wmem_free_skb(sk, skb);  
	}  

do_error:  
	if (copied)  
		goto out;  
out_err:  
	err = sk_stream_error(sk, flags, err);  
	release_sock(sk);  
	return err;  
}  

tcp_sendmsg中,涉及对OOB数据的处理主要有:

1、在调用tcp_send_mss确定分片大小的时候:
1
2
3
4
5
6
7
8
9
static int tcp_send_mss(struct sock *sk,int *size_goal, int flags)
{
	intmss_now;
	mss_now= tcp_current_mss(sk);

	/*如果是OOB数据,large_allowed=0,关闭TSO*/
	*size_goal= tcp_xmit_size_goal(sk, mss_now, !(flags & MSG_OOB));
	returnmss_now;
}

如果是OOB数据,其实是关闭了TSO功能,这样做的原因是:天知道各个网卡芯片在执行分片的时候咋个处理TCP报头中的URG标志和urgent point

2、在确定何时开始执行分片的发送的时候:

如果是OOB数据,即使当前已经积累了一整个分片,也不会想普通的数据一样执行发送(tcp_push),而是继续积累直到用户下发的数据全部分片或者snd_buf/内存用尽。

3、执行tcp_push的时候:

在用户下发的数据全部分片或者snd_buf/内存用尽后,进入tcp_push执行发送操作(所有的OOB数据,都会通过这个接口来执行发送)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static inline void tcp_push(struct sock*sk, int flags, int mss_now,
						 int nonagle)
{
	if(tcp_send_head(sk)) {
		structtcp_sock *tp = tcp_sk(sk);
		if(!(flags & MSG_MORE) || forced_push(tp))
			tcp_mark_push(tp,tcp_write_queue_tail(sk));      
			  /*tcp_mark_urg设置tp->snd_up,标识进入OOB数据发送模式,设置urgent point
			  指向urgentdata接受后的第一个字符*/
		tcp_mark_urg(tp,flags);
		__tcp_push_pending_frames(sk,mss_now,
					  (flags & MSG_MORE) ? TCP_NAGLE_CORK :nonagle);
	}
}

发送处理

使用struct tcp_sock中的snd_up来标识当前的urgent point,同时也使用该数据来判断当前是否处于urgent data发送模式,在普通数据的发送模式中tcp_sock::snd_up总是和tcp_sock::snd_una相等,只有在有urgent data发送的时候,才在tcp_push—>tcp_mark_urg中设置为urgentpoint,进入到urgent data的处理模式

在tcp_transmit_skb中的以下代码段负责urgent data相关的处理:

1
2
3
4
5
6
7
8
9
if (unlikely(tcp_urg_mode(tp) && before(tcb->seq, tp->snd_up))) {  
	if (before(tp->snd_up, tcb->seq + 0x10000)) {  
		th->urg_ptr = htons(tp->snd_up - tcb->seq);  
		th->urg = 1;  
	} else if (after(tcb->seq + 0xFFFF, tp->snd_nxt)) {  
		th->urg_ptr = htons(0xFFFF);  
		th->urg = 1;  
	}  
}  

只要当前待发送的skb的seq在tcp_sock记录的urgent point前面,就需要在报头中对URG标志置位,同时如果tcp_sock记录的urgent point。如果该报文的seq距离大于16为能表示的最大值,就置TCP报头中的urgent point为65535。

切换回普通模式:

在收到对方ACK的处理流程tcp_ack—>tcp_clean_rtx_queue中:

1
2
if (likely(between(tp->snd_up, prior_snd_una, tp->snd_una)))  
	tp->snd_up = tp->snd_una;  

报文体现

根据对发送代码的分析,可以看到:如果用户使用MSG_OOB数据发送一段比较长(若干个MSS)的数据,那么线路上的报文应该是分成了若干组,每组由若干个长度为MSS的报文构成,组内的每个报文有一样的urgent pointer,指向下一组报文的起始seq,每一组的长度最长为65535。


TCP的URG标志和内核实现之三:接收的实现

大致的处理过程

TCP的接收流程:在tcp_v4_do_rcv中的相关处理(网卡收到报文触发)中,会首先通过tcp_check_urg设置tcp_sock的urg_data为TCP_URG_NOTYET(urgent point指向的可能不是本报文,而是后续报文或者前面收到的乱序报文),并保存最新的urgent data的sequence和对于的1 BYTE urgent data到tcp_sock的urg_data (如果之前的urgent data没有读取,就会被覆盖)。

用户接收流程:在tcp_recvmsg流程中,如果发现当前的skb的数据中有urgent data,首先拷贝urgent data之前的数据,然后tcp_recvmsg退出,提示用户来接收OOB数据;在用户下一次调用tcp_recvmsg来接收数据的时候,会跳过urgent data,并设置urgent data数据接收完成。 相关的数据结构和定义

tcp_sock结构:

1、 urg_data成员,其高8bit为urgent data的接收状态;其低8位为保存的1BYTE urgent数据。urgent data的接收状态对应的宏的含义描述:

1
2
3
4
5
#defineTCP_URG_VALID 0x0100  /*urgent data已经读到了tcp_sock::urg_data*/

#defineTCP_URG_NOTYET   0x0200  /*已经发现有urgent data,还没有读取到tcp_sock::urg_data*/

#defineTCP_URG_READ       0x0400  /*urgent data已经被用户通过MSG_OOB读取了*/

2、 urg_seq成员,为当前的urgent data的sequence

流程详情

TCP的接收过程

在tcp_rcv_established的slow_path中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
slow_path:  
	if (len < (th->doff << 2) || tcp_checksum_complete_user(sk, skb))  
		goto csum_error;  
	/* 
	 *  Standard slow path. 
	 */  
	if (!tcp_validate_incoming(sk, skb, th, 1))  
		return 0;  
step5:  
	if (th->ack &&  
		tcp_ack(sk, skb, FLAG_SLOWPATH | FLAG_UPDATE_TS_RECENT) < 0)  
		goto discard;  
	tcp_rcv_rtt_measure_ts(sk, skb);  
	/* 处理紧急数据. */  
	tcp_urg(sk, skb, th);  

也就是在报文的CRC验证和sequence验证完成后,就会通过tcp_urg来处理接收到的urgent data :

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
static void tcp_urg(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th)  
{  
	struct tcp_sock *tp = tcp_sk(sk);  
  
	/*收到了urgent data,则检查和设置urg_data和urg_seq成员*/  
	if (th->urg)  
		tcp_check_urg(sk, th);  
  
	/* Do we wait for any urgent data? - normally not... 
	发现了有urgent data,但是还没有保存到tp->urg_data*/  
	if (tp->urg_data == TCP_URG_NOTYET) {  
		u32 ptr = tp->urg_seq - ntohl(th->seq) + (th->doff * 4) -  
			  th->syn;  
  
		/* Is the urgent pointer pointing into this packet? */  
		if (ptr < skb->len) {  
			u8 tmp;  
			if (skb_copy_bits(skb, ptr, &tmp, 1))  
				BUG();  
			tp->urg_data = TCP_URG_VALID | tmp;  
			if (!sock_flag(sk, SOCK_DEAD))  
				sk->sk_data_ready(sk, 0);  
		}  
	}  
}  

检查和设置urg_data和urg_seq成员的处理函数tcp_check_urg的具体流程

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
static void tcp_check_urg(struct sock *sk, const struct tcphdr *th)  
{  
	struct tcp_sock *tp = tcp_sk(sk);  
	u32 ptr = ntohs(th->urg_ptr);  
	/*两种urgent point的解析方式: 
	一是指向urgent data之后的第一个字节 
	二是执行urgent data的结束字节(RFC1122) 
	sysctl_tcp_stdurg被设置表示当前采用的是第二种模式 
	不需要把urgent point -1来指向urgent data的结束字节*/  
	if (ptr && !sysctl_tcp_stdurg)  
		ptr--;  
	ptr += ntohl(th->seq);  
  
	/* Ignore urgent data that we've already seen and read.  
	如果copied_seq已经大于urgent point,那么对于从tcp_rcv_established 
	来执行的,前面的tcp_validate_incoming已经拒绝了这种报文( 
	接收窗口外),这里要处理的是哪种情形?*/  
	if (after(tp->copied_seq, ptr))  
		return;  
  
	/* Do not replay urg ptr. 
	 * 
	 * NOTE: interesting situation not covered by specs. 
	 * Misbehaving sender may send urg ptr, pointing to segment, 
	 * which we already have in ofo queue. We are not able to fetch 
	 * such data and will stay in TCP_URG_NOTYET until will be eaten 
	 * by recvmsg(). Seems, we are not obliged to handle such wicked 
	 * situations. But it is worth to think about possibility of some 
	 * DoSes using some hypothetical application level deadlock. 
	 */  
	/*  这种情况什么时候发生?没搞明白*/  
	if (before(ptr, tp->rcv_nxt))  
		return;  
  
	/* Do we already have a newer (or duplicate) urgent pointer?  
	如果当前已经进入urg数据读取模式,且urgent point不大于当前 
	保存的值,那么之前已经开始了读取tp->urg_seq对应的 
	urgent 数据,无需重复处理了*/  
	if (tp->urg_data && !after(ptr, tp->urg_seq))  
		return;  
  
	/* Tell the world about our new urgent pointer.*/  
	sk_send_sigurg(sk);  
  
	/* We may be adding urgent data when the last byte read was 
	 * urgent. To do this requires some care. We cannot just ignore 
	 * tp->copied_seq since we would read the last urgent byte again 
	 * as data, nor can we alter copied_seq until this data arrives 
	 * or we break the semantics of SIOCATMARK (and thus sockatmark()) 
	 * 
	 * NOTE. Double Dutch. Rendering to plain English: author of comment 
	 * above did something sort of  send("A", MSG_OOB); send("B", MSG_OOB); 
	 * and expect that both A and B disappear from stream. This is _wrong_. 
	 * Though this happens in BSD with high probability, this is occasional. 
	 * Any application relying on this is buggy. Note also, that fix "works" 
	 * only in this artificial test. Insert some normal data between A and B and we will 
	 * decline of BSD again. Verdict: it is better to remove to trap 
	 * buggy users. 
	 */  
	 /*用户下一次要读取的数据就是用户还没有读取的urgent数据 
	且当前存在新的用户未读取数据*/  
	if (tp->urg_seq == tp->copied_seq && tp->urg_data &&  
		!sock_flag(sk, SOCK_URGINLINE) && tp->copied_seq != tp->rcv_nxt) {  
		struct sk_buff *skb = skb_peek(&sk->sk_receive_queue);  
		tp->copied_seq++;  
		if (skb && !before(tp->copied_seq, TCP_SKB_CB(skb)->end_seq)) {  
			__skb_unlink(skb, &sk->sk_receive_queue);  
			__kfree_skb(skb);  
		}  
	}  
  
	tp->urg_data = TCP_URG_NOTYET;  
	tp->urg_seq = ptr;  
  
	/* Disable header prediction. */  
	tp->pred_flags = 0;  
}  

用户接收数据接口

用户接收URG数据的接口

在用户接收数据的tcp_recvmsg函数中,如果用户通过MSG_OOB来接收数据,会进入tcp_recv_urg处理

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
static int tcp_recv_urg(struct sock *sk, struct msghdr *msg, int len, int flags)  
{  
	struct tcp_sock *tp = tcp_sk(sk);  
  
	/* No URG data to read.  
	用户已经读取过了*/  
	if (sock_flag(sk, SOCK_URGINLINE) || !tp->urg_data ||  
		tp->urg_data == TCP_URG_READ)  
		return -EINVAL; /* Yes this is right ! */  
  
	if (sk->sk_state == TCP_CLOSE && !sock_flag(sk, SOCK_DONE))  
		return -ENOTCONN;  
	/*当前的tp->urg_data为合法的数据,可以读取*/  
	if (tp->urg_data & TCP_URG_VALID) {  
		int err = 0;  
		char c = tp->urg_data;  
		/*标识urgent data已读*/  
		if (!(flags & MSG_PEEK))  
			tp->urg_data = TCP_URG_READ;  
  
		/* Read urgent data. */  
		msg->msg_flags |= MSG_OOB;  
  
		if (len > 0) {  
			if (!(flags & MSG_TRUNC))  
				err = memcpy_toiovec(msg->msg_iov, &c, 1);  
			len = 1;  
		} else  
			msg->msg_flags |= MSG_TRUNC;  
  
		return err ? -EFAULT : len;  
	}  
  
	if (sk->sk_state == TCP_CLOSE || (sk->sk_shutdown & RCV_SHUTDOWN))  
		return 0;  
  
	/* Fixed the recv(..., MSG_OOB) behaviour.  BSD docs and 
	 * the available implementations agree in this case: 
	 * this call should never block, independent of the 
	 * blocking state of the socket. 
	 * Mike <pall@rz.uni-karlsruhe.de> 
	 */  
	return -EAGAIN;  
}  
用户接收普通数据的接口中的相关处理

在用户接收数据的tcp_recvmsg函数中,在查找到待拷贝的skb后,首先拷贝urgent data数据前的数据,然后退出接收过程,在用户下一次执行tcp_recvmsg的时候跳过urgent data,设置urgent data读取结束

查找到准备拷贝的skb后的处理:

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
found_ok_skb:  
/* Ok so how much can we use? */  
used = skb->len - offset;  
if (len < used)  
	used = len;  
  
/* 当前有urg_data数据*/  
if (tp->urg_data) {  
	u32 urg_offset = tp->urg_seq - *seq;  
	/*urgent data在当前待拷贝的数据范围内*/  
	if (urg_offset < used) {  
		if (!urg_offset) {/*待拷贝的数据就是urgent data,跨过该urgent data, 
		只给用户读取后面的数据*/  
			if (!sock_flag(sk, SOCK_URGINLINE)) {  
				++*seq;  
				urg_hole++;  
				offset++;  
				used--;  
				if (!used)  
					goto skip_copy;  
			}  
		}   
		} else/*指定只拷贝urgent data数据之前的,完成后在下一次循环 
		开始的位置,会退出循环,返回用户;下一次用户调用tcp_recvmsg 
		就进入到上面的分支了*/  
			used = urg_offset;  
	}  
}   
1
2
3
4
5
6
7
8
9
skip_copy:  
		/*用户读取的数据跨过了urgent point,设置读取结束 
		开启fast path*/  
		if (tp->urg_data && after(tp->copied_seq, tp->urg_seq)) {  
			tp->urg_data = 0;  
			tcp_fast_path_check(sk);  
		}  
		if (used + offset < skb->len)  
			continue;  

在接收完urgent data数据前的所有数据之后, tcp_recvmsg的以下代码片段得到执行,这段代码退出当前接收过程,提示用户有urgent data数据到来,需要用MSG_OOB来接收

1
2
3
4
5
6
7
8
if (tp->urg_data && tp->urg_seq == *seq) {  
	if (copied)  
		break;  
	if (signal_pending(current)) {  
		copied = timeo ? sock_intr_errno(timeo) : -EAGAIN;  
		break;  
	}  
}  

后记

TCP的urg数据,由于定义和实现上的混乱,当前已经不建议使用,但是为了兼容之前已经已经存在的实现,该机制会长期在内核中存在,如果不了解该机制及其内核行为,有可能就很难解释一些奇怪的问题:比如某段代码不小心地造成send接口事实上设置了MSG_OOB,就会造成接收端少了一个BYTE。