[译]UUID和Linux:你需要知道的一切

本文为 UUIDs and Linux: Everything you ever need to know [Update] 的翻译。

UUID在Linux中为何如此特别?我[原作者]不知道!但是这儿有你需要知道的、关于UUID在Linux下的一切!

背景

UUID是128位长的数字,表示为32位的16进制数字,被用于在软件开发中、在没有上下文的情况下唯一地标识信息。RFC 4122描述了UUID的规范,一个UUID的例子是:

13152fae-d25a-4d78-b318-74397eb08184

UUID在Linux最熟悉的场景应该是块设备的标识符了吧。Windows世界中的UUID以Microsoft的全局唯一标识符GUID的形式出现,这些标识符在Microsoft的组件对象模型[COM]中使用。

UUID以各种变体生成:最初大多数是从计算机的MAC派生的,随后使用了基于name的散列值。关于这个问题,有多少UUID,有多大概率会生成一个已有的UUID,这是来自维基百科上 UUID 的文章的一些数字:

在100年里,每秒生成十亿个UUID,生成重复的UUID的概率约为50%。如果每个人都有6亿个UUID,下一个UUID重复的概率约为50%。

fstab中的用法

就像之前提到的,UUID在Linux下被经常用于标识块设备。想象一下,你有两个硬盘通过USB连接上,它们不是持久存储,只能依赖两个设备的名字来区分:有时第一个USB硬盘是“sda”,有时它是“sdb”。所以要唯一寻址正确的磁盘,比如在 /etc/fstab 中,你必须添加如下条目:

UUID=9043278a-1817-4ff5-8145-c79d8e24ea79 /boot ext3 defaults 0 2

对于块设备,uuid是存储在超级块中的。
但请注意,当您使用LVM快照时,不应在fstab中使用UUID。 有关更多详细信息,请参阅下面的“何时不使用它们”。

Linux下的实现和生成

在Linux下UUID在file中生成,你可以通过 /drivers/char/random.c 来生成新的UUID:

$ cat /proc/sys/kernel/random/uuid
eaf3a162-d770-4ec9-a819-ec96d429ea9f

uuidgen,以及ext 2/3/4 工具 E2fsprogs 使用程序库 libuuid 来生成UUID:

$ uuidgen 
f81cc383-aa75-4714-aa8a-3ce39e8ad33c

Bash中如何得到UUID

UUID中最有趣的部分很可能是如何获取硬盘的当前UUID。 如前面已经提到的,有两种主要的方法来获取它们:在特殊目录中的 ls 命令和工具 blkid。

第一,在 /dev/disk/by-uuid 中执行 ls 命令。这个目录包含了名字为UUID,链接到“真正的”块设备文件。对于你很难辨别安装了什么磁盘的情况下,这是很方便的。

$ ls -l /dev/disk/by-uuid
lrwxrwxrwx 1 root root 10 11. Oct 18:02 53cdad3b-4b01-4a6c-a099-be1cdf1acf6d -> ../../sda2

第二,blkid 工具是util-linux软件包的一部分。它提供了一个界面来查询特定设备,并且还支持搜索标签。

$ blkid /dev/sda1
/dev/sda1: LABEL="/" UUID="ee7cf0a0-1922-401b-a1ae-6ec9261484c0" SEC_TYPE="ext2" TYPE="ext3"

这儿还有更多方法!让我们安装 hwinfo:

$ hwinfo --block
[...]
  UDI: /org/freedesktop/Hal/devices/volume_uuid_3e953ee0_79f2_4d94_98b3_5f49ad652b7c
[...]
  Device Files: [...] /dev/disk/by-uuid/3e953ee0-79f2-4d94-98b3-5f49ad652b7c
[...]

就像你看到的,hwinfo列出了大量有关硬件的数据——包括设备的UUID。 当你获取更多关于块设备的信息时使用它。

或者 udevadm?它是udev提供的、从udev数据库查询数据的工具。udev数据库包含了udev系统的所有信息,因此UUID信息只是一部分。 如果你正在写一个“现代”的脚本,它很好地与Linux标准工具集成,我想我会使用udev。 但是对于快速直接的、一次性的命令行工具,和hwinfo一样,它包含了太多的信息。

$ udevadm info -q all -n /dev/sda1|grep uuid
S: disk/by-uuid/9043278a-1817-4ff5-8145-c79d8e24ea79
E: [...] /dev/disk/by-uuid/9043278a-1817-4ff5-8145-c79d8e24ea79
E: ID_FS_UUID=9043278a-1817-4ff5-8145-c79d8e24ea79
E: ID_FS_UUID_ENC=9043278a-1817-4ff5-8145-c79d8e24ea79

在这种情况下,有时也提到udevinfo。 但是,它已被弃用,大多数发行版不再包含。 另外,另一种常提到的检索UUID的方法是从程序库 /lib/vol/vol_id。 但是如bug #476379所述,vol_id只是一个私有的udev函数。因为此接口不稳定它不应该被外部程序(或人)使用。此外,整个库可能会在将来删除——实际上已在某些发行版中删除了。

如何在GUI程序中获取UUID

如果你不善于使用shell,KDE-GUI工具也可以用来查找UUID:/usr/bin/kcmshell4 devinfo

设置UUID

就像评论中提到的,设置UUID有时也是必要的。因为UUID是超级块的一部分,所以根据文件系统的不同,可能要使用不同的工具。比如ext文件系统可以使用tune2fs:

# tune2fs -U new_uuid /dev/sdaX

什么时候不使用UUID

Zhenech 提到,并不是所有的地方都适合使用UUID。

因为挂载两个具有相同的UUID的文件系统是不可行的,所以需要额外关注LVM快照(或者克隆磁盘):因为具有同样的UUID,它们可能会挂载失败。

XFS:dm-2文件系统也会有重复的UUID,也无法挂载。

处理该问题的一种方法是更改UUID,另一种办法是挂载的时候带上nouuid选项。

[译]为了更具有创造力,还是不要那么高效吧

本文为 To Get More Creative, Become Less Productive 的翻译。

高效和创造力之间存在着根本性的矛盾,在意识到这一点之前,管理者无法获取更多的创意。

高效的人们以一种系统化的方式来完成一系列必须完成的任务。他们以稳定的、可衡量的方式来完成目标。他们非常有效率、并且能够有效地利用自己的时间。

创造力——可不是这样的。

创造性的发展需要时间和空间。虽然我们可以系统地从事与创造力有关的活动,但是很难系统化创造力本身。特别是,创造力是知识的基础。 几乎所有的创意想法都涉及人们寻找现有知识的新用途——一些旧想法的新组合。James Dyson通过类比锯木厂发明了他的真空吸尘器。 Fiona Fairhurst通过研究鲨鱼皮肤设计了更快的泳装。 George de Mestral通过了解苍耳发明了魔术贴。

这意味着从他们需要创造力的开始,人们就需要时间来学习与他们的工作无关的东西,以便他们有广泛而深入的知识库。

此外,有创造力的企业很少涉及稳定和可衡量的进步。 相反,创造力包括尝试许多不同的可能性,以及在找到正确的解决方案之前努力尝试几个盲目的岔路。

但这些活动——建立一个知识库并探索它——需要时间。 很难简单地安排几个小时在这里和那里进行创造性的追求。相反,有些时候,需要花时间学习一个新的知识领域,或者与同事进行漫长的谈话,来理清楚一个新的想法。 所以很多创意活动可能看起来像是一个可疑的,直到突破来临。

许多公司想要他们的员工获得更多创造力,这其中的主要原因就是生产力和创造力之间的这种差异。

公司通常基于生产率来评估员工。 更重要的是,他们根据假设他们将雇佣高效的人来制定他们的招聘计划。 他们希望组织中的人员能够取得明显的进步。 他们致力于发展尽责的人——能够完成任务的人。

如果一个组织真正想要创造力,它必须开始招聘更多的人,而不是完成任务、仅仅让公司保持运作所需的那么点人。Google的20%的时间,即鼓励员工将20%的时间花在新想法上的行动,已经执行了很多时间了。对于如何在公司实施这项政策,已经有了很多的讨论,我的观点是,如果你要给每个人一个机会来发展他们的创造力,你需要多雇用10-20%的人。

当要开发一个新的想法的时候,管理者还需要为员工提供一些时间安排上的灵活性。 给予某人10-20%的自由时间来发展他们的创造力,这并不一定意味着他们每周将花费4-8小时来发展创造力。 相反,可能有几个星期,一些人专注于他们需要完成的任务,还有几个星期或者几天来验证一个“兔子洞”的想法。

但是,仅仅为员工提供创造性所需的时间和灵活性是不够的。 管理者必须奖励员工从事的、提供创造性解决方案的任务,如学习新事物,发展新技能,与同事广泛的对话,以及尝试不成功的想法。

发展创造力的管理工作是可行的,但是它需要那些对效率痴迷的管理者们放松对工作时间的约束。

Linux 3.4中acpi_pad的一个Bug分析

今天,同事被Bug #42981坑了,看了同事发的文章,觉得有必要分析下这个bug。这篇博客主要讲acpi_pad是如何工作的。


模块注册

module加载的时候首先会执行init函数,acpi_pad注册的init函数acpi_pad_init。acpi_pad_init最终调用driver_register来将acpi_pad_driver.drv 注册到系统中。

acpi_pad_driver的定义如下:

static struct acpi_driver acpi_pad_driver = {
        .name = "processor_aggregator",
        .class = ACPI_PROCESSOR_AGGREGATOR_CLASS,
        .ids = pad_device_ids,
        .ops = {
                .add = acpi_pad_add,
                .remove = acpi_pad_remove,
        },
};

没有.drv 字段?看下struct acpi_driver定义

struct acpi_driver {
        char name[80];
        char class[80];
        const struct acpi_device_id *ids; /* Supported Hardware IDs */
        unsigned int flags;
        struct acpi_device_ops ops;
        struct device_driver drv;
        struct module *owner;
};

这边需要注意的是,acpi_driver里面直接嵌套了一个device_driver结构体,而不是用指针引用,这一点很重要。

但是,acpi_pad_driver.drv没有初始化!后来找了找,发现了初始化的代码(在acpi_bus_register_driver中):

driver->drv.name = driver->name;
driver->drv.bus = &acpi_bus_type;
driver->drv.owner = driver->owner;

这个时候,driver是指向acpi_pad_driver的指针。

acpi_bus_type的定义如下:

struct bus_type acpi_bus_type = {
        .name           = "acpi",
        .suspend        = acpi_device_suspend,
        .resume         = acpi_device_resume,
        .match          = acpi_bus_match,
        .probe          = acpi_device_probe,
        .remove         = acpi_device_remove,
        .uevent         = acpi_device_uevent,
};

注册了driver之后,我们就应该关注acpi_device_probe函数了,这个函数真正在sysfs中创建了idlecpus文件(这个文件是用户控制acpi_pad特性的入口)。

static int acpi_device_probe(struct device * dev) 函数是被内核调用的,相当于回调:

static int acpi_device_probe(struct device * dev)
{
        struct acpi_device *acpi_dev = to_acpi_device(dev);
        struct acpi_driver *acpi_drv = to_acpi_driver(dev->driver);
        int ret;

        ret = acpi_bus_driver_init(acpi_dev, acpi_drv);
        //。。。。。。
        return ret;
}

to_acpi_driver就是container_of宏,可以将struct acpi_driver的drv的地址,转化微acpi_driver的地址(就是根据子结构体地址,获取父级结构体地址):

#define container_of(ptr, type, member) ({                      \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
        (type *)( (char *)__mptr - offsetof(type,member) );})

acpi_device_probe函数最终在acpi_bus_driver_init中调用acpi_pad_driver.ops.add 函数,即acpi_pad_add函数。最终使用在acpi_pad_add_sysfs中将idlecpus绑定到了sysfs:

static int acpi_pad_add_sysfs(struct acpi_device *device)
{
        int result;
        result = device_create_file(&device->dev, &dev_attr_idlecpus);
        //。。。。。。
        return 0;
}

dev_attr_idlecpus的定义:

static DEVICE_ATTR(idlecpus, S_IRUGO|S_IWUSR,
        acpi_pad_idlecpus_show,
        acpi_pad_idlecpus_store);

被展开为结构体变量定义struct device_attribute dev_attr_idlecpus

该文件的读写函数分别是acpi_pad_idlecpus_show和acpi_pad_idlecpus_store。


至此,acpi_pad模块加载完成,idlecpus文件也在sysfs中加载完成了。

通过acpi_pad修改cpu状态

根据bug重现说明

to make the failure more likely:

# echo 1 > rrtime
# echo 31 > idlecpus; echo 0 > idlecpus
# echo 31 > idlecpus; echo 0 > idlecpus
# echo 31 > idlecpus; echo 0 > idlecpus
(it usually takes only a few attempts)

etc. until the echo does not return

我们通过idlecpus节点,先空置31个cpu,再激活,多试几次就可以重现该bug了。

在此过程中,调用了acpi_pad_idlecpus_store函数:

static ssize_t acpi_pad_idlecpus_store(struct device *dev,
        struct device_attribute *attr, const char *buf, size_t count)
{
        unsigned long num;
        if (strict_strtoul(buf, 0, &num))
                return -EINVAL;
        mutex_lock(&isolated_cpus_lock);
        acpi_pad_idle_cpus(num);
        mutex_unlock(&isolated_cpus_lock);
        return count;
}

将用户输入的buf转化为long,获取isolated_cpus_lock锁(这个就导致了前面提到的bug)。然后通过acpi_pad_idle_cpus将用户需要的cpu数置空:

static void acpi_pad_idle_cpus(unsigned int num_cpus)
{
        // 对cpu想关数据加锁
        get_online_cpus();

        // 当前在线cpu,将要置空的cpu 这两个数字,选一个小的
        num_cpus = min_t(unsigned int, num_cpus, num_online_cpus());
        // 将置空的cpu数目同步到num_cpus个
        set_power_saving_task_num(num_cpus);
        // 对cpu相关数据解锁
        put_online_cpus();
}

set_power_saving_task_num的逻辑很简单,根据当前的power_saving_thread线程数量,少了就通过create_power_saving_task补足,多了就通过destroy_power_saving_task去掉一些。


destory_power_saving_task调用kthread_stop来结束多余的power_saving_thread线程。kthread_stop设置对应kthread的should_stop为1,然后等待该kthread结束:

kthread->should_stop = 1;
wake_up_process(k);
wait_for_completion(&kthread->exited);

但是它在等待kthread结束的时候,还拿着isolated_cpus_lock这个锁呢!!


我们来看下power_saving_thread到底干了啥,导致了bug。

static int power_saving_thread(void *data)
{
        //。。。。。。

        while (!kthread_should_stop()) {
                int cpu;
                u64 expire_time;

                try_to_freeze();

                /* round robin to cpus */
                if (last_jiffies + round_robin_time * HZ < jiffies) {
                        last_jiffies = jiffies;
                        round_robin_cpu(tsk_index);
                }
                //。。。。。。
        }
        //。。。。。。
}

看起来,没有问题,我们来看下round_robin_cpu的代码:

static void round_robin_cpu(unsigned int tsk_index)
{
        //。。。。。。
        mutex_lock(&isolated_cpus_lock);
        cpumask_clear(tmp);
        // 。。。。。
        mutex_unlock(&isolated_cpus_lock);

        set_cpus_allowed_ptr(current, cpumask_of(preferred_cpu));
}

代码中有对isolated_cpus_lock加锁的操作,这导致了这个bug。


Bug是如何出现的

一边,acpi_pad_idlecpus_store函数拿到ioslated_cpus_lock锁,调用kthread_stop等待power_saving_thread结束。

另一边,要结束的这个kthread/power_saving_thread,在round_robin_cpu函数中,等待isolated_cpu_lock锁。

两个kthread互相等待,成了死锁。

Continue reading Linux 3.4中acpi_pad的一个Bug分析

php-fpm的reload过程

背景

谈谈PHP的Reload操作中提到reload会让sleep提前结束,所以就探究了下fpm的reload操作如何实现。

本文在php7.0 fpm下分析,process_control_timeout设置不为0。


重启信号

首先,我们从PHP源码可以知道,fpm的reload操作实际上就是对fpm进程发送了USR2信号。

fpm的master进程中,fpm_signals_init_main函数通过sigaction注册了信号处理函数sig_handler

int fpm_signals_init_main() /* {{{ */
{
	struct sigaction act;

	// 。。。。。。

	memset(&act, 0, sizeof(act));
	act.sa_handler = sig_handler;
	sigfillset(&act.sa_mask);

	if (0 > sigaction(SIGTERM,  &act, 0) ||
	    0 > sigaction(SIGINT,   &act, 0) ||
	    0 > sigaction(SIGUSR1,  &act, 0) ||
	    0 > sigaction(SIGUSR2,  &act, 0) ||
	    0 > sigaction(SIGCHLD,  &act, 0) ||
	    0 > sigaction(SIGQUIT,  &act, 0)) {

		zlog(ZLOG_SYSERROR, "failed to init signals: sigaction()");
		return -1;
	}
	return 0;
}
/* }}} */

简而言之,通过sigfillset设置为block掉所有的信号,然后通过sigaction设置对应的信号处理函数。


当我们reload fpm时,systemctl向fpm的master进程发送USR2信号,执行函数sig_handler函数

static void sig_handler(int signo) /* {{{ */
{
	static const char sig_chars[NSIG + 1] = {
		[SIGTERM] = 'T',
		[SIGINT]  = 'I',
		[SIGUSR1] = '1',
		[SIGUSR2] = '2',
		[SIGQUIT] = 'Q',
		[SIGCHLD] = 'C'
	};
	char s;
        // ***
	s = sig_chars[signo];
	zend_quiet_write(sp[1], &s, sizeof(s));
	errno = saved_errno;
}
/* }}} */

关键点在zend_quiet_write,它就是write函数。sig_handler函数就是向sp[1]中写入了一个字符串2。

此处需要注意的是,sp[0]和sp[1]是通过socketpair创建的本地套接字。


master开始重启

之前的信号处理函数,在信号发生的时候会被调用,但是程序的主逻辑仍然不会被打乱,那fpm master进程怎么知道要reload呢?

答案就在fpm_event_loop中,这是master进程的事件循环。

在循环之前,我们需要用sp[0]创建一个struct fpm_event_s,添加到监听的fd中:

int fpm_event_set(struct fpm_event_s *ev, int fd, int flags, void (*callback)(struct fpm_event_s *, short, void *), void *arg) /* {{{ */
{
	if (!ev || !callback || fd < -1) {
		return -1;
	}
	memset(ev, 0, sizeof(struct fpm_event_s));
	ev->fd = fd;
	ev->callback = callback;
	ev->arg = arg;
	ev->flags = flags;
	return 0;
}
/* }}} */

然后将这个struct fpm_event_s,也就是代码中的ev,添加到监听的fd中。

实际上,这个添加过程也和fpm不同的异步模型有关(都是由对应fpm_event_module_s的add方法实现的),比如epoll的实现就是将ev参数整体放到epoll_event的data.ptr中的。(poll的add可以参考源码

当所有的fd都添加了之后(当然不仅仅是signal相关的fd咯),我们就可以使用wait方法等待事件来临了。(epoll和poll也都各自实现了wait方法)

好,回到sig_handler给sp[1]写了个字符串2。wait方法接到了信号,拿到对应的ev,调用 fpm_event_fire ,实际上就是调用了ev的callback方法,就是fpm_got_signal方法

static void fpm_got_signal(struct fpm_event_s *ev, short which, void *arg) /* {{{ */
{
	char c;
	int res, ret;
	int fd = ev->fd;

	do {
		res = read(fd, &c, 1);

		switch (c) {
			// 。。。。。。
			case '2' :                  /* SIGUSR2 */
				zlog(ZLOG_DEBUG, "received SIGUSR2");
				zlog(ZLOG_NOTICE, "Reloading in progress ...");
				fpm_pctl(FPM_PCTL_STATE_RELOADING, FPM_PCTL_ACTION_SET);
				break;
		}

		if (fpm_globals.is_child) {
			break;
		}
	} while (1);
	return;
}
/* }}} */

如果接收到了字符串2,则执行

fpm_pctl(FPM_PCTL_STATE_RELOADING, FPM_PCTL_ACTION_SET)

实际上就这么几行

void fpm_pctl(int new_state, int action) /* {{{ */
{
	switch (action) {
		case FPM_PCTL_ACTION_SET :
			//。。。。。。
			fpm_signal_sent = 0;
			fpm_state = new_state;

			zlog(ZLOG_DEBUG, "switching to '%s' state", fpm_state_names[fpm_state]);
			/* fall down */

		case FPM_PCTL_ACTION_TIMEOUT :
			fpm_pctl_action_next();
			break;
		//。。。。。
	}
}
/* }}} */

即,将fpm_state设置为FPM_PCTL_STATE_RELOADING后,没有break,继续执行fpm_pctl_action_next

static void fpm_pctl_action_next() /* {{{ */
{
	int sig, timeout;

	if (!fpm_globals.running_children) {
		fpm_pctl_action_last();
	}

	if (fpm_signal_sent == 0) {
		if (fpm_state == FPM_PCTL_STATE_TERMINATING) {
			sig = SIGTERM;
		} else {
			sig = SIGQUIT;
		}
		timeout = fpm_global_config.process_control_timeout;
	} else {
		if (fpm_signal_sent == SIGQUIT) {
			sig = SIGTERM;
		} else {
			sig = SIGKILL;
		}
		timeout = 1;
	}

	fpm_pctl_kill_all(sig);
	fpm_signal_sent = sig;
	fpm_pctl_timeout_set(timeout);
}
/* }}} */

即,给所有子进程发送SIGQUIT信号。

这边还有一个fpm_pctl_timeout_set,这个等会讨论。


子进程处理信号

父进程发送完信号了,就该子进程处理啦。

子进程只有SIGQUIT交给sig_soft_quit处理。子进程初始化完成后,收到了SIGQUIT信号,由sig_soft_quit处理,最终调用fcgi_terminate处理:

void fcgi_terminate(void)
{
	in_shutdown = 1;
}

就是将in_shutdown设置为1。


子进程退出

子进程的循环主体在fcgi_accept_request中,其中多出判断in_shutdown,若为1则直接退出:


超时处理

前面提到的超时处理的回调函数fpm_pctl_timeout_set。执行了如下操作:

fpm_pctl(FPM_PCTL_STATE_UNSPECIFIED, FPM_PCTL_ACTION_TIMEOUT);

在这种条件下,发送的信号变成了SIGTERM,直接退出了子进程。


为何sleep会被打断?

我们可以看到,sleep的实现就是系统调用sleep(php_sleep是sleep的一个宏):

/* {{{ proto void sleep(int seconds)
   Delay for a given number of seconds */
PHP_FUNCTION(sleep)
{
	zend_long num;

	if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &num) == FAILURE) {
		RETURN_FALSE;
	}
	php_sleep((unsigned int)num);

}
/* }}} */

sleep函数执行时,此时进程的状态是S:

interruptible sleep

此时一旦有信号触发,立马处理信号,比如我们刚刚说过的SIGQUIT,结束了之后发现,sleep执行完了。

因为sleep的说明写了啊:

sleep() makes the calling thread sleep until seconds seconds have
       elapsed or a signal arrives which is not ignored.

需要注意的是,php的sleep没有return系统调用sleep的返回值,所以即使信号打断了sleep,也仅仅是跳过sleep继续执行而已。

Fedora上完美的使用QQ

目前看下来,Linux使用QQ还是Deepin比较好。

CrossOver上的QQ目前不能记住密码,其他还好,比如聊天啥的也是可以的。

Deepin的QQ是目前最好的解决方案。

安装如下:

  • 首先,从百度网盘下载crossover-15_15.0.3-1_all-free.deb 。解压其中的opt/cxoffice/ 到/opt/cxoffice/
  • 其次,从镜像站点下载Deepin的QQ,apps.com.qq.im_8.1.17255deepin11_i386.deb
  • 解压apps.com.qq.im_8.1.17255deepin11_i386.deb 中的opt/cxoffice/support/apps.com.qq.im 到/opt/cxoffice/support/apps.com.qq.im
  • 然后执行/opt/cxoffice/bin/crossover
  • 在CrossOver点击QQ启动即可。

Continue reading Fedora上完美的使用QQ

Fedora KDE 25下安装fcitx输入法

首先,安装fcitx之后,只要是接受了GTK_IM_MODULE 、QT_IM_MODULE 、XMODIFIERS 环境变量的程序,都可以使用fcitx输入法了。

但是,即使我在~/.xprofile 里面写了这三个export语句,程序也没法读到这三个环境变量。

后来发现,这里面有两个坑:

  1. Fedora KDE的display manager换成了SDDM,而在SDDM的配置文件中,默认不会执行~/.xprofile 里面的语句。
  2. SDDM问题解决后,发现fcitx依赖的imsettings,会设置这三个变量,所以在~/.xprofile 里面export的变量,其实会被imsettings覆盖。

Continue reading Fedora KDE 25下安装fcitx输入法

composer的自动加载机制解读

按照composer文档的说法,如果是composer项目,只需要在开始的时候require 'vendor/autoload.php'即可享受类的自动加载特性。可是这是如何实现的呢?

vendor/autoload.php

以Laravel 5.1项目为例,vendor/autoload.php文件只做了两件事情:

  1. include vendor/composer/autoload_real.php
  2. 调用ComposerAutoloaderInitb6d254015e39cf5090fb84fdb1ed664b::getLoader()

vendor/composer/autoload_real.php仅仅定义了ComposerAutoloaderInitb6d254015e39cf5090fb84fdb1ed664b类和composerRequireb6d254015e39cf5090fb84fdb1ed664b函数。(b6d254015e39cf5090fb84fdb1ed664b应该是类似id一样的东西,确保每次不同)

接下来我们关注下ComposerAutoloaderInitb6d254015e39cf5090fb84fdb1ed664b::getLoader()做了哪些事情。

ComposerAutoloaderInitb6d254015e39cf5090fb84fdb1ed664b::getLoader()

首先,这个类的loader只会初始化一次,第二次是直接返回已经存在的loader了:

if (null !== self::$loader) {
    return self::$loader;
}

如果是第一次调用,先注册['ComposerAutoloaderInitb6d254015e39cf5090fb84fdb1ed664b', 'loadClassLoader'],然后new一个\Composer\Autoload\ClassLoader 作为loader,然后立马取消注册loadClassLoader

接下来就一步一步处理各种autoload了。

autoload_namespaces.php

直接从vendor/composer/autoload_namespaces.php中返回的就是$namespace$path的一个数组。通过\Composer\Autoload\ClassLoader::set方法来记录这些信息:

if (!$prefix) {
    // 如果namespace/prefix为空,则在其他PSR-0规则中查不到,才会到这个规则中
    $this->fallbackDirsPsr0 = (array) $paths;
} else {
    // 以后根据对应的namespace/prefix查找类的时候,第一个字母用来加速
    // 使用第一个字母作为一级key,可以减少二级key的冲突
    $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}

autoload_psr4.php

直接从该文件的返回值是$namespace$path的数组。通过\Composer\Autoload\ClassLoader::setPsr4来记录此信息:

if (!$prefix) {
    // 如果namespace/prefix为空,则其他PSR-4规则查找不到的时候,会到此规则中查找
    $this->fallbackDirsPsr4 = (array) $paths;
} else {
    // TODO
    $length = strlen($prefix);
    // 略去错误检查

    // TODO
    $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
    // 用来根据类前缀找path
    $this->prefixDirsPsr4[$prefix] = (array) $paths;
}

autoload_classmap.php

直接从该文件的返回值是$classname$path的数组。通过\Composer\Autoload\ClassLoader::addClassMap来记录此信息:

if ($this->classMap) {
    $this->classMap = array_merge($this->classMap, $classMap);
} else {
    $this->classMap = $classMap;
}

这些都处理完之后,\Composer\Autoload\ClassLoader::register[$this, 'loadClass']注册为autoload函数(通过spl_autoload_register)。

autoload_files.php

直接从该文件的返回值是$fileIdentifier到文件路径的映射,通过之前定义的composerRequireb6d254015e39cf5090fb84fdb1ed664b函数来require每个文件。

这个函数在require文件的时候同时也设置了$GLOBALS['__composer_autoload_files'][$fileIdentifier]的值为true。

\Composer\Autoload\ClassLoader::loadClass

这个函数的作用是装载一个类。

if ($file = $this->findFile($class)) {
    includeFile($file);

    return true;
}

\Composer\Autoload\ClassLoader::findFile

这个函数是composer装载类的重点。

  1. 首先是从classMap里面找$class对应的文件,如果有,直接返回。
  2. 然后从prefixDirsPsr4找到前缀符合的文件,如果找到,直接返回。(那个prefixLengthsPsr4就是用来判断需要从$class去掉的前缀长度)
  3. 接下来,直接从fallbackDirsPsr4对应的目录中查找文件。
  4. PSR-0加载
    • namespaced class name变换规则:\A_namespace\a_class=>/A_namespace/a/class.php 
    • PEAR-like class name变换规则:\A_namespace\a_class=>/A/namespace/a/class.php 

    prefixesPsr0中查找(和prefixDirsPsr4类似),直接找到对应的文件,返回。

  5. 如果没有,直接从fallbackDirsPsr0中尝试加载。

通过DNS认证来部署Let’s Encrypt

原来是通过http认证的方式来完成ACME 的 Identifier Validation Challenges,但是内网的机器就无法完成这个认证,今天看了下,LE支持dns认证了,所以实践了一下。

首先安装Certbot

然后执行

certbot -d ssl-test.robberphex.com --manual --preferred-challenges dns certonly

对于MacOS用户来说,可以执行certbot –config-dir /usr/local/etc/letsencrypt –logs-dir /usr/local/var/log/letsencrypt –work-dir /usr/local/var/lib/letsencrypt -d ssl-test.robberphex.com –manual –preferred-challenges dns certonly 

  • 需要输入邮箱

  • 同意用户协议

  • 同意记录IP

  • 设置域名的TXT记录


    比如图中,设置_acme-challenge.ssl-test.robberphex.com的TXT记录为x-P6A_dQ4_ggZtPvX_bOUeaY7hSM_IS6o-Gzj3h7LBw,然后回车。

  • 提示证书生成成功


我们来一个最简版的配置:

server {
    listen       8443 ssl;
    server_name  ssl-test.robberphex.com;

    ssl_certificate /usr/local/etc/letsencrypt/live/ssl-test.robberphex.com/fullchain.pem;
    ssl_certificate_key /usr/local/etc/letsencrypt/live/ssl-test.robberphex.com/privkey.pem;

    location / {
        default_type "text/plain";
        return 200 "pong";
    }
}

curl测试(不需要设置DNS):

$ curl -i --resolve ssl-test.robberphex.com:8443:127.0.0.1 https://ssl-test.robberphex.com:8443/
HTTP/1.1 200 OK
Server: nginx/1.10.2
Date: Wed, 14 Dec 2016 07:33:41 GMT
Content-Type: text/plain
Content-Length: 4
Connection: keep-alive

pong

浏览器测试(需要设置DNS或者hosts文件):

证书有效

Continue reading 通过DNS认证来部署Let’s Encrypt

使用repox搭建sbt/maven镜像

最近sbt的速度实在是不能忍受了,所以使用repox搭建了sbt镜像。

搭建过程没有什么好说的,直接sbt assembly,然后

java -Xmx512m -jar target/scala-2.11/repox-assembly-0.1-SNAPSHOT.jar

就好了。


但是发现了两个repox的问题:

  1. 下载文件时,服务器全部下载完后,才能传输给sbt(不支持nginx那种“流式代理”),这在下载大文件时尤其明显。
  2. 有的时候,pom文件总是404
    比如curl https://repo1.maven.org/maven2/org/w3c/css/sac/1.3/sac-1.3.pom -I的结果是200,curl http://106.75.27.110:8078/org/w3c/css/sac/1.3/sac-1.3.pom -I的结果就是404,重试好几次都不行;重启repox就好了。

Flume在hdfs产生大量日志文件的问题

线上两台flume向hdfs写日志,但是后来发现每5分钟产生的日志文件数量很多(远远超过两个的数量)。

后来找了一个时间观察下日志,发现如下日志:

16/12/13 11:38:11 INFO hdfs.BucketWriter: Closing idle bucketWriter hdfs://xxx/xx/xxx_log_20161213/.xxx_log.1481600173693.tmp at 1481600291480

根据日志找代码,发现是BucketWriter在配置了hdfs.idleTimeout的情况下,会在超时后关闭不活跃的日志文件

flume官方文档对此参数的描述如下:

Timeout after which inactive files get closed (0 = disable automatic closing of idle files)

找到原因后,当然就很简单了,由于此参数默认是0,直接删除这个参数的相关配置就好了。每5分钟产生的日志数量就是两个了。