[译]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 中,你必须添加如下条目:

阅读更多

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

本文为 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是如何工作的。


模块注册

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

acpi_pad_driver的定义如下:

1
2
3
4
5
6
7
8
9
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 的定义

1
2
3
4
5
6
7
8
9
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中):

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

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

acpi_bus_type的定义如下:

1
2
3
4
5
6
7
8
9
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) 函数是被内核调用的,相当于回调:

1
2
3
4
5
6
7
8
9
10
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的地址(就是根据子结构体地址,获取父级结构体地址):

1
2
3
#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:

1
2
3
4
5
6
7
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的定义:

1
2
3
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函数:

1
2
3
4
5
6
7
8
9
10
11
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数置空:

1
2
3
4
5
6
7
8
9
10
11
12
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结束:

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

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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的代码:

1
2
3
4
5
6
7
8
9
10
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互相等待,成了死锁。

阅读更多

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
}
/* }}} */
阅读更多

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启动即可。
阅读更多

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覆盖。

阅读更多

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()做了哪些事情。

ComposerAutoloaderInit<id>::getLoader()

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

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

如果是第一次调用,先注册['ComposerAutoloaderInitb6d254015e39cf5090fb84fdb1ed664b', 'loadClassLoader'],然后new一个\Composer\Autoload\ClassLoader 作为loader,然后立马取消注册loadClassLoader。 接下来就一步一步处理各种autoload了。

autoload_namespaces.php

阅读更多

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

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

安装

首先安装Certbot,按照官网操作即可: https://certbot.eff.org/instructions

生成证书

然后执行

1
2
3
certbot -d ssl-test.robberphex.com \
--manual --preferred-challenges \
dns certonly

对于MacOS用户来说,可以执行

1
2
3
4
5
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,然后回车。
  • 提示证书生成成功

配置

阅读更多

使用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分钟产生的日志数量就是两个了。

阅读更多