event

event 是 libeven reactor 模式中的基本处理单位,所有事件都是基于 event 去添加,事件类型有:

  • 普通 IO
  • signal 事件
  • 定时器 以上事件都可以设置超时返回时间,成为相应的超时事件。 详细可以看看之前的这篇文章

event_add

本质调用event_add_internal,调用前加了把锁。

  1. 只要是超时事件,分配 min_heap 资源

  1. signal 事件的多线程 add 处理,在 signal hander 时,又要 add 相同事件,则需要排队 signal 事件和普通 io 的处理是不一样的。

  1. 以上两种事件分别添加到对应的数据结构里,再将 event 添加到 acitive_queue 里,标记状态为 INSERTED;

添加到数据结构

  • evmap_io_add 普通 io 事件添加

event 按 fd 作为索引,添加到链表数组中. 因为要当 queue 用,先进先出,所以是insert_tail,remove_head 如果 fd 没被添加过,通过 eventop(IO 复用模型)的 add 把 fd 添加到 IO 复用的监控列表里。 如果已经是 IO 多路复用已经添加过的 fd(event list 不为空),那就在队尾插入该事件。

  • evmap_signal_add signal 事件的添加

用的数据结构与上面的 evmap_io_add 一致。 这篇文章讲了 signal 这种异步机制是怎么纳入框架。sig_hander 里用 socketpair 写,读端用 event 标记为 READ…

event_queue_insert

设置 queue 状态,然后依据状态把 event 添加到 base 的管理链表中.

#define EVLIST_TIMEOUT	0x01
#define EVLIST_INSERTED	0x02
#define EVLIST_SIGNAL	0x04
#define EVLIST_ACTIVE	0x08
#define EVLIST_INTERNAL	0x10
#define EVLIST_INIT	0x80
  • EVLIST_INSERTED: 刚添加的事件,在 base 的 event_queue 中
  • EVLIST_ACTIVE: base 的 active_queues 中,类似 event_io_map 的链表数组,不同优先级索引寻访不同的链表头 active_queues[pri].
  • EVLIST_TIMEOUT: 超时事件,大量的相等时间的超时用 common_timeout_list 管理,否则用 min_heap_push;

特别的处理

  • n 个 event 可以从多个线程添加,处理多线程
  • 等待 event 时,添加 event? io 复用中包括一个专门的内部 event, add 时,向该 fd 发送一个字节,即可唤醒 IO 复用,然后添加新的事件(fd).

event_add_internal 代码附录

static inline int
event_add_internal(struct event *ev, const struct timeval *tv,
    int tv_is_absolute)
{
    struct event_base *base = ev->ev_base;
    int res = 0;
    int notify = 0;

    //锁确认
	EVENT_BASE_ASSERT_LOCKED(base);
	_event_debug_assert_is_setup(ev);

	event_debug((
		 "event_add: event: %p (fd "EV_SOCK_FMT"), %s%s%scall %p",
		 ev,
		 EV_SOCK_ARG(ev->ev_fd),
		 ev->ev_events & EV_READ ? "EV_READ " : " ",
		 ev->ev_events & EV_WRITE ? "EV_WRITE " : " ",
		 tv ? "EV_TIMEOUT " : " ",
		 ev->ev_callback));

EVUTIL_ASSERT(!(ev->ev_flags & ~EVLIST_ALL));

	/*
	 * prepare for timeout insertion further below, if we get a
	 * failure on any step, we should not change any state.
	 */
	 //如果有超时时间tv,而且该event之前没有设置过超时,分配min_heap
	if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {
		if (min_heap_reserve(&base->timeheap,
			1 + min_heap_size(&base->timeheap)) == -1)
			return (-1);  /* ENOMEM == errno */
	}

	/* If the main thread is currently executing a signal event's
	 * callback, and we are not the main thread, then we want to wait
	 * until the callback is done before we mess with the event, or else
	 * we can race on ev_ncalls and ev_pncalls below. */
	 //signal类的多线程event不能在callback期间同时添加,需挂起等待
#ifndef _EVENT_DISABLE_THREAD_SUPPORT
	if (base->current_event == ev && (ev->ev_events & EV_SIGNAL)
	    && !EVBASE_IN_THREAD(base)) {
		++base->current_event_waiters;
		EVTHREAD_COND_WAIT(base->current_event_cond, base->th_base_lock);
	}
#endif

	if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&
	    !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {
		//普通io
		if (ev->ev_events & (EV_READ|EV_WRITE))
			res = evmap_io_add(base, ev->ev_fd, ev);
		else if (ev->ev_events & EV_SIGNAL)
		//signal
			res = evmap_signal_add(base, (int)ev->ev_fd, ev);
		//激活队列
		if (res != -1)
			event_queue_insert(base, ev, EVLIST_INSERTED);
		if (res == 1) {
			/* evmap says we need to notify the main thread. */
			notify = 1;
			res = 0;
		}
	}

	/*
	 * we should change the timeout state only if the previous event
	 * addition succeeded.
	 */
	if (res != -1 && tv != NULL) {
		struct timeval now;
		int common_timeout;

		/*
		 * for persistent timeout events, we remember the
		 * timeout value and re-add the event.
		 *
		 * If tv_is_absolute, this was already set.
		 */
		if (ev->ev_closure == EV_CLOSURE_PERSIST && !tv_is_absolute)
			ev->ev_io_timeout = *tv;

		/*
		 * we already reserved memory above for the case where we
		 * are not replacing an existing timeout.
		 */
		if (ev->ev_flags & EVLIST_TIMEOUT) {
			/* XXX I believe this is needless. */
			if (min_heap_elt_is_top(ev))
				notify = 1;
			event_queue_remove(base, ev, EVLIST_TIMEOUT);
		}

		/* Check if it is active due to a timeout.  Rescheduling
		 * this timeout before the callback can be executed
		 * removes it from the active list. */
		if ((ev->ev_flags & EVLIST_ACTIVE) &&
		    (ev->ev_res & EV_TIMEOUT)) {
			if (ev->ev_events & EV_SIGNAL) {
				/* See if we are just active executing
				 * this event in a loop
				 */
				if (ev->ev_ncalls && ev->ev_pncalls) {
					/* Abort loop */
					*ev->ev_pncalls = 0;
				}
			}

			event_queue_remove(base, ev, EVLIST_ACTIVE);
		}

		gettime(base, &now);

		common_timeout = is_common_timeout(tv, base);
		if (tv_is_absolute) {
			ev->ev_timeout = *tv;
		} else if (common_timeout) {
			struct timeval tmp = *tv;
			tmp.tv_usec &= MICROSECONDS_MASK;
			evutil_timeradd(&now, &tmp, &ev->ev_timeout);
			ev->ev_timeout.tv_usec |=
			    (tv->tv_usec & ~MICROSECONDS_MASK);
		} else {
			evutil_timeradd(&now, tv, &ev->ev_timeout);
		}

		event_debug((
			 "event_add: timeout in %d seconds, call %p",
			 (int)tv->tv_sec, ev->ev_callback));

		event_queue_insert(base, ev, EVLIST_TIMEOUT);
		if (common_timeout) {
			struct common_timeout_list *ctl =
			    get_common_timeout_list(base, &ev->ev_timeout);
			if (ev == TAILQ_FIRST(&ctl->events)) {
				common_timeout_schedule(ctl, &now, ev);
			}
		} else {
			/* See if the earliest timeout is now earlier than it
			 * was before: if so, we will need to tell the main
			 * thread to wake up earlier than it would
			 * otherwise. */
			if (min_heap_elt_is_top(ev))
				notify = 1;
		}
	}

	/* if we are not in the right thread, we need to wake up the loop */
	if (res != -1 && notify && EVBASE_NEED_NOTIFY(base))
		evthread_notify_base(base);

	_event_debug_note_add(ev);

	return (res);
}

参考

Read More

前言

一直很好奇嵌入式linux是的启动流程,以前是做芯片的,对单纯芯片的启动非常熟悉,现在的工作基本在user space,现在回过头来研究下底层的启动。

了解完整的linux启动过程,可以看看linux引导过程内幕,很详细。

另外一直以来对项目的基于squash文件系统的ota升级颗粒度过大颇有微词,看看能否有改进之处。

bin打包

  • bin打包时,指定了烧录的的地址分区

注意这些分区文件都是按squashfs or yaffs2文件系统制作好的,并且在制作bin之前进行压缩以节省空间。

../tools/bin/isp pack_image ../ISPBOOOT.BIN \
        xboot0         uboot0    \
        xboot1         0x0060000 \
        uboot1         0x0060000 \
        uboot2         0x0060000 \
        env            0x0020000 \
        env_redund     0x0020000 \
        ecos           0x0300000 \
        kernel         0x0260000 \
        rootfs         0x0600000 \
        pq             0x0020000 \
        tcon           0x0020000 \
        iop_car        0x0020000 \
        runtime_cfg    0x0020000 \
        version_info   0x0020000 \
        vendordata     0x0020000 \
        logo           0x0480000 \
        isp_logo       0x0500000 \
        kvdb           0x2000000 \
        system         0x3000000 \
        spsdk          0x1000000 \
        leavn          0x3000000 \
        nvm            0x8200000 \
        update         0xC800000 \
        vd_restore     0x0040000

uboot启动

略..

load kernel

略…

load rootfs

init进程

  • 这个分区值最终在启动后,在/proc/mtd里体现

  • init进程 /etc/inittab

init进程都是知道是程序的第1个启动程序,它将根据/etc/inittab文件来创建其他的子进程,比如调用脚本文件配置IP地址,挂载其他的文件系统,最后启动shell等。

  # Start an "respawn" shell on the serial port
  ttyS0::respawn:-/bin/sh
  #ttyS0::askfirst:-/bin/ash
  # Stuff to do when restarting the init process
  ::restart:/sbin/init
  # Stuff to do before rebooting
  ::ctrlaltdel:/sbin/reboot
  ::shutdown:/bin/umount -a -r
  ::shutdown:/sbin/swapoff -a

etc/init.d/rcS分析

该脚本是如何执行的还待分析,仅知道肯定是初始启动的脚本。

  • 启动脚本里 etc/init.d/rcS 挂载mtd的文件到工作目录

对应了前面的flash烧写的内容

system_mtdid=`cat /proc/mtd | grep system | sed "s/:.*//" | sed "s/mtd//"`
...
#/ota/system 是只读的, squashfs文件系统
mount -t squashfs  /dev/blockrom$system_mtdid /ota/system

  • 还有几个目录是按yaffs2文件系统挂载的,应该是可读写的
mount -t yaffs2 -o noatime /dev/mtdblock$nvm_mtdid /data
  • 如果检测到有更新,即存在reinstall_file文件,则执行更新

这里应该就是ota升级的诀窍

if [ -f /update/reinstall_file ];then
	cd /update
	rows=`ls -l *.squash |awk 'END{print NR}'`
	cols=`ls -l *.squash |awk 'END{print NF}'` #need to test!!!

	[ $rows -eq 0 -o $cols -eq 0 ] && echo "Not Found Any Squash File, Check Update Or Not!"
	for i in $(seq 1 $rows)
	do
        	FILENAME=`ls -l *.squash | sed -n "${i}p" | awk '{print $NF}'`
	        PARTITION=`echo $FILENAME | sed "s/\..*//"`
		MOUNTPOINT=$PARTITION

		if [ -f /ota/$MOUNTPOINT/install.sh ];then
			/ota/$MOUNTPOINT/install.sh
		fi
	done

	rm -f /update/reinstall_file
	cd -
fi
  • 执行应用层的启动脚本
/tdGUI/start.sh

start.sh

这个启动层脚本 初略看下把

  • 运行了某个install.sh脚本的某个命令

其核心效果就是创建从目录a到目录b的软链接。 前面提到文件挂载其实就是在目录b(/ota/leavn..),而软链接全部在/data/usr/下 方便统一管理。

do_merge_dir: link /data/usr/bin/AndroidPhone --> /ota/leavn/package/LeAVN/bin/AndroidPhone
  • 加载硬件厂家负责的驱动

有MCU,驱动屏的。

insmod /ota/spsdk/drivers/i2c-core.ko
insmod /ota/spsdk/drivers/i2c-gemini.ko
insmod /ota/spsdk/drivers/i2c-dev.ko
insmod /ota/spsdk/drivers/evdev.ko
insmod /ota/spsdk/drivers/gt9xx.ko

  • 运行了一个应用程序管理器,其实也就是把之前统一管理的app,按需求启动起来,

直接用service systemctrl不就好? 比如按顺序启动了这些程序

"aaa"
"bbb"
"ccc"
"ddd"
  • 启动udev

管理热拔插的

  • 初始化 bsp sdk.sh

主要工作即export各种库的路径,加载各种usb sd 声卡的驱动等等。

总结

大概的启动流程其实和标准linux并无二致。从中看出squash文件如果要修改颗粒度:

  • 思路1

传输原始文件,颗粒度小 但是要按实际的文件组织形式,再车机上打包squash、 问题在于车机打包,将会很慢很慢。

  • 思路2

在最开始分区的时候就细分?

Read More

花了 1 天搭起这个 jekyll+github 的博客平台,希望这个 blog 能坚持下去.

写作

vscode 写 markdown, jekyll 的博客框架, 文章往_posts 里填.

图床

vscode 整个了一个 paste image to qiniu的插件,使用时在编辑模式,shift+ctrl+v即可自动上传,并粘贴出路径。配置如下,

    //截图文件产生在当前文件目录的imgs目录下
    "pasteImage.path": "/home/bravo/assets/",

    //一个有效的七牛 AccessKey 签名授权。
    "pasteImageToQiniu.access_key": "xxx",

    // 一个有效的七牛 SecretKey 签名授权。
    "pasteImageToQiniu.secret_key": "xxx",

    // 七牛图片上传空间。
    "pasteImageToQiniu.bucket": "images",

    // 七牛图片上传路径,参数化命名。
    "pasteImageToQiniu.remotePath": "${fileName}",

    // 七牛图床域名。
    "pasteImageToQiniu.domain": "xxx",

    // 图片本地保存位置
    "pasteImageToQiniu.localPath": "/home/bravo/assets/",

另外作图买了Process on的个人版,可以当作图的图床。

坑记录

  • ajax 被墙

之前一直开着 ss 不觉得,一关掉竟然刷新不出 navigation bar 了。原因找了下是因为 search 插件使用的 js 用到了 ajax,里面访问了 google 的地址,前端东西不懂的 只能猜是这卡死了。

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>

所有用到的地方全部改了就好

<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
  • disqus 被墙

这位兄台,遇到了,解决方案

Read More