作为便签,防止工作跟丢
工作之后,每天要处理的事情多而琐碎,手里一件事还没做完,又会来很多新的事,先记录到工作日志,心里有个轻重缓急,按优先级一一处理,完成一件勾掉一件,会很有成就感。
便于回溯和总结
今天做的事,可能一两个月后就记不起来了,有时候需要回溯当时做事的细节,搜索一下就能找到(报加班的时候也方便查到底哪天加了班 :joy:。
有时候需要站在事后去看自己这周、这个月、这一年完成了些什么,能更清晰地看出自己有哪些不足,哪些值得改进,工作日志是第一手的原材料。
记录常用的信息
工作中有些信息的使用频率很高,比如一些常用的脚本和命令,又比如一些关键文档的归档路径,每次到资料里去查,其实只是想拷贝那一串字符,不如直接放到工作日志里,省时省心。
理论上所有能记录结构化信息的工具都可以作为工作日志的载体,你甚至可以用手写的笔记本,但这里只推荐使用 Typora,理由如下:
第三方商业笔记软件(如印象笔记和幕布)的优点是在移动端也支持得很好,但这类软件出于商业目的,通常不愿意提供将笔记作为通用格式导出的功能,个人也不愿意把重要信息完全交给一家公司托管。
如果有家公司能把基于 Markdown 的多平台云笔记软件做大做成功(意味着它不太可能倒闭,倒闭了我的信息也不会丢),我还是很愿意为之付费的。
通常我的工作日志以月为单位,标题就是简单的“20xx年xx月工作日志”。
每篇日志都包括这几个目录:
整体计划
首先要记录公司/部门这个月的整体计划(用简单的一两句话概括),这些信息通常能从各类会议中获得,可以随时补充更新。
其次记录个人在这个月规划的几件重要的事,作为部门的一员,首先要确保自己的计划与部门的相匹配,因为在正确的方向上你的努力才能得到认可。有余力的情况下也可以规划一些额外目标,如个人能力提升或做感兴趣的项目。
最后还可以加几句提醒自己的话,比如“把事情一次做对”,这个就见仁见智了。
工作日志
这里记录每天的工作信息,列工作是早上要做的第一件事,通常我会每天加一个三级标题,方便快速导航,比如“12月1日(周二)”。
是否要加上星期几,这个因人而异,我喜欢加是因为能一眼看出一个月哪几天加班了 :smile:
标题下面不要直接用一个列表记录所有工作,这样不够清晰,我会按照最重要的两到三块工作划分一下,比如:
可以用打勾或者划线的方式标记已经完成的工作。我不会再按“紧急”或“重要”对这些工作排序,这样真的很费时间,特别优先的工作加粗表示即可。
下班的时候可能还没有完成所有的工作,这再正常不过了,把剩下的工作移到明天,用括号标记进度,就可以回家啦。
如果没有这步转移,不仅会导致日志有很多冗余信息,还可能把重要的事遗漏掉。
工作池
工作池,或者工作队列,用于存放没有明确完成日期的工作,这些工作的紧急程度相对较低。
我的工作日志会从上往下写,因为当前写的内容最靠近工作池,做完眼前的事情后,就可以随时从工作池补充新的工作内容。
工作池的组织方式与工作日志相同,同样按照最重要的几块工作划分,但可以分得更细,有时间就梳理下,没时间也无伤大雅。
常用信息
存放一些经常会用到但又记不住的信息,比如常用的脚本和命令,目的是提高查询/复制粘贴的效率。
恰当的信息密度
所谓“恰当”,就是既不要事无巨细,影响工作效率,又不要过于简略,以至于以后读不懂。
记得设置 Typora 的自动保存
日志内容丢的时候,别怪我没提醒你。
开始新月份的时候,记得把所有没完成的工作拷贝出来
在华为的一年多时间,我犯过错误,也得到过褒奖,也算在这个庞杂而矛盾的世界中积攒了些许生存经验。
记录工作日志,只是想帮助自己更轻松地工作,工作占据了我们大半的人生,如果不能从工作中获得很多乐趣,希望我们至少能高效地工作,留出更多的时间陪伴家人、丰富生活。
]]>我认为,未来会更多地属于那些告别大教堂、拥抱集市的人们。
这不是说个人的远见和才华不再重要;而是在我看来,未来的成功者只是从自己的远见和才华开始工作,然后通过有效的社区合作,将其不断地放大。
开放式的文化会最终胜利,这或许不是因为”开放”在道德上正确,或者”封闭”在道德上错误,而只是因为开放式合作可以在一个问题上投入多几个数量级的技术工时,封闭的世界无法赢得这样的竞争。
本指南献给闯荡银河系的勇者们,愿他们也能领略到内核世界的绝美风光。
目录 | 内容 | 备注 |
---|---|---|
kernel | 内核核心组件 | 除非绝对必要,否则不要向该目录添加内容。 |
mm | 架构无关的内存管理代码 | |
fs | 所有文件系统实现代码 | |
init | 内核初始化相关代码 | |
ipc | 进程间通信机制 | |
block | 块设备相关代码 | |
drivers | 各类驱动程序 | 源码中所占空间最大,但只有少量成员会编译到。 |
sound | 声卡驱动程序 | 尽管有独立的目录,但和其他设备驱动程序类似。 |
net | 网络实现代码 | 包括核心和各种协议两部分。 |
lib | 通用库例程 | 包括各种数据结构和数据压缩的例程。 |
include | 所有公开导出函数的头文件 | |
usr | cpio 相关实现 | |
security | 各类安全模块 | 主要包括 LSM 模块和密钥管理代码。 |
crypto | 加密层文件 | 如各种加密算法的实现。 |
virt | 内核虚拟化代码 | |
arch | 体系结构相关文件 | 每种体系结构都有独立子目录,内部类似于顶层目录。 |
samples | 编程范例 | 包括内核和用户态编程。 |
scripts | 脚本和使用程序 | 用于编译内核、检查补丁格式等任务。 |
tools | 内核裁剪配置工具 | 将源码编译成目标文件,连接合并为可运行的 zImage。 |
Documentation | 内核文档 | 虽然其中很多过时了。 |
LICENSES | 各类相关开源许可证 | |
MAINTAINERS | 内核各模块维护者联系方式 | |
Kconfig | 编译配置文件 | 决定需要编译哪些代码。 |
Kbuild | 组件式构建配置文件 | |
Makefile | 主 Makefile | 定义 C 编译器、链接器的调用路径。 |
内核开发中一个常见的场景是补丁移植,将高版本内核的修复补丁或特性补丁回合到低版本内核中,往往因为补丁上下文的改动导致无法直接合入,这时候就需要我们手动进行移植。
使用 git am
尝试合入开源补丁:
1 | git am xxx.patch |
该命令会尝试合入补丁,如果合入顺利,就直接根据补丁头的信息进行提交,如果有冲突,会回退所有的修改并告知哪里有冲突。
你可以加上
-s
选项以在提交信息中附加自己的签名。
出现冲突时的处理。
首先判断冲突是否是因为补丁内容已经存在于源码中,如果是,就直接跳过补丁:
1 | git am --skip |
使用 git apply
合入未冲突的部分修改,并通过 --reject
选项导出冲突内容到一个 .rej 文件中:
1 | git apply --reject xxx.patch |
使用 vim -O
同时打开 .rej 文件和源文件,以便手动适配合入:
1 | vim -O crashfile.rej crashfile |
根据补丁上下文,从 .rej 文件复制对应的修改到原文件,删除应该删除的行。
清理 .rej 文件并使用 git add
添加修改到文件,最后完成提交:
1 | rm -f crashfile.rej |
合入补丁后,根据需要补充补丁头信息:
1 | git commit --amend |
生成单个新补丁:
1 | git format-patch --subject-prefix="PATCH version_prefix" -1 |
一次生成完整的补丁集:
1 | git format-patch --subject-prefix="PATCH version_prefix" --cover-letter [commit-ID] |
检查补丁:
1 | scripts/checkpatch.pl xxx.patch |
发送补丁:
1 | git send-email *.patch -to "maintainer email" --cc="kernel.openeuler@huawei.com" --from="your email" --suppress-cc=all |
kdump 是一种内核崩溃转储机制,能够在系统崩溃时自动转储内存,以用于后续分析。
kdump 工作过程如下:
系统内核启动的时候,为 capture 内核预留一块内存空间,该空间无法被当前内核访问;
这块空间的大小及其在内存中的偏移可通过启动参数中的
crashkernel=size[@offset]
指定。
内核启动完成后,kdump 服务执行 kexec -p
把 capture 内核载入预留的内存;
如果当前内核发生崩溃,就自动把控制权交给 capture 内核(capture 内核仅使用预留内存,因此其余内存数据不会被改动),由该内核把崩溃内核内存中的数据写入到 dump 文件;
写完 dump 文件中,capture 内核自动重启。
这里不过多介绍 kdump 的设置,读者可以在各发行版的文档中找到(例如红帽),直接介绍如何通过 vmcore 文件获取系统崩溃信息。
性能分析的“瑞士军刀”。
审计(audit)这个概念最初来自于会计领域,意为对一个组织或个体的账户和财务状态进行正式评估:
An audit is an “independent examination of financial information of any entity, whether profit oriented or not, irrespective of its size or legal form when such an examination is conducted with a view to express an opinion thereon”.
投资者无法判断企业的财务报表有没有水分,因此雇佣专业的第三方(会计事务所)作为裁判来鉴别财务报表的真实性,以防作弊。
审计有两个关键词:
在信息安全领域,被审计的对象就是信息系统,审计的维度不再局限于真实性,还包括机密性、完整性、可用性等多个维度,
具体到 Linux 系统中,审计过程的第三方就是内核,“正式”意味着在内核中根据设定的规则生成审计消息。
日志和审计常常被一起提及,因为它们都记录了系统的行为,区别在于:
日志属于调测领域,审计属于安全领域,这是两者本质的区别。
整个审计子系统的各部分构成如下:
内核中的 audit 子系统是审计系统的核心,
除此以外,审计子系统也提供了一系列用户态组件方便用户使用:
/etc/audit/auditd.conf
文件用于配置 auditd 服务进程的行为。/etc/audit/audit.rules
/etc/audit/rules.d/*
首先查看内核审计子系统的状态:
1 | auditctl -s |
enabled 参数表示系统审计开关,可通过 -e
选项设置,取值包括 0|1|2
三种:
0
表示禁用审计;1
表示启用审计(默认);2
表示启用审计并锁定配置,锁定后所有配置无法更改,直到重新启动。注:如果出现
audit support not in kernel
字样,表示审计模块未加载进内核,可能是内核启动参数中加了aduit=0
的限制,需要删除该字段或将其修改为audit=1
。
内核审计系统启用的情况下,能够正常获取审计信息,但还需要启动用户态的 auditd 服务收集和记录日志:
1 | service auditd status # 查看auditd服务状态 |
可通过 /etc/audit/auditd.conf
文件配置 auditd 服务,常见配置包括:
配置项 | 含义 | 推荐设置 |
---|---|---|
write_logs | 是否写日志 | yes |
log_file | 日志路径 | /var/log/audit/audit.log |
max_log_file | 日志文件大小上限(以 MB 为单位) | 8 |
max_log_file_action | 日志文件到达上限后的动作 | keep_logs |
space_left | 磁盘剩余空间下限 | 75 |
space_left_action | 磁盘剩余空间不足下限后的动作 | email 或 exec(执行脚本) |
flush | 日志文件更新后磁盘的同步策略 | sync 或 data |
更多参数和解释可以在 auditd.conf(5) 手册页中找到。
更新此配置文件后需要重启 auditd 服务才能生效:
1 | service auditd restart |
auditctl 命令可被用于配置审计规则,例如审计对 /etc/ssh/sshd_config
文件的所有访问
-w
选项指定要审计的文件路径。-p
选项描述系统调用对文件的访问动作:-k
选项指定过滤关键词,生成的审计日志消息会包含这个关键词,后续可用于日志过滤。-a
选项-S
选项-F
选项更多选项和解释可以在 audit.rules(7) 手册页中找到。
如果需要每次启动都生效,可以通过配置 /etc/audit/audit.rules
文件或在 /etc/audit/rules.d
目录新增配置文件实现,配置文件的内容就是加在 auditctl 命令后的选项和参数。
常见配置如下:
不配置审计规则:
1 | -D |
审计登录事件,添加如下规则行:
1 | -w /var/log/faillog -p wa -k logins |
审计内核模块加载、卸载事件,添加如下规则行:
1 | -w /sbin/insmod -p x -k modules |
在所有配置项后增加 -e 2
可
配置完成后,可以使用 -l
选项查看系统当前审计规则:
1 | auditctl -l # 查看所有生效的审计规则 |
使用 -D
选项删除审计规则(通常会配置 -k
选项使用,不加的话就是删除所有规则):
1 | auditctl -D # 删除所有审计规则 |
一方面,配置的审计规则越多,监控的事件就越多,系统会更加安全,另一方面,大量审计规则也会带来性能影响,所以用户需要根据实际情况设置规则。
审计日志记录了审计活动的结果,通常位于 /var/log/audit/audit.log
路径下,通过阅读审计日志,我们能得到许多关于系统状态的信息。
以一个典型的审计事件为例:
1 | type=SYSCALL msg=audit(1434371271.277:135496): arch=c000003e syscall=2 success=yes exit=3 a0=7fff0054e929 a1=0 a2=1fffffffffff0000 a3=7fff0054c390 items=1 ppid=6265 pid=6266 auid=1000 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts0 ses=113 comm="cat" exe="/usr/bin/cat" key="sshconfigchange" |
type
:表示审计事件的类型,type=SYSCALL
表示日志由系统调用所触发;
msg
:表示时间戳和事件 ID(同一审计事件可能有多条记录,但都有相同的事件 ID);
arch
:表示 CPU 体系结构(c000003e
对应 x86,c00000b7
对应 arm);
syscall
:表示触发事件的系统调用号,可通过 ausyscall 命令查询:
1 | ausyscall 189 # 查询189号系统调用 |
success
:表示系统调用返回成功与否;
exit
:表示系统调用的返回值;
a0-a3
:传递给系统调用的前四个参数;
ppid
:表示父进程 ID;
pid
:表示触发系统调用的进程 ID;
auid
uid
comm
:表示触发审计的命令;
exe
:表示触发审计的命令的文件路径;
key
:表示审计规则中定义的标签;
audit 日志文件的信息很多,日志很不方便,
生成有关可执行文件的审计事件的报告:
1 | aureport -x |
也可以结合 ausearch 命令生成报告:
1 | ausearch --start today --loginuid 500 --raw | aureport -f --summary |
更多选项及使用方法,请参考 aureport(8) 手册页。
首先调用 audit_log_start
函数申请一个缓冲区:
1 | struct audit_buffer *audit_log_start(struct audit_context *ctx, gfp_t gfp_mask, int type); |
然后调用 audit_log_format
往缓冲区中写日志:
1 | void audit_log_format(struct audit_buffer *ab, const char *fmt, ...); |
最后调用 audit_log_end
结束写日志:
1 | void audit_log_end(struct audit_buffer *ab); |
1 |
|
审计机制属于内核最核心的部分,因此代码都在 kernel
目录下,首先对文件有个大致的了解:
audit.h
:头文件,定义审计机制使用的通用结构体和函数;audit.c
:提供核心审计机制;auditsc.c
:实际了对系统调用的审计;auditfilter.c
:包含了过滤审计事件的机制;audit_fsnotify.c
audit_tree.c
audit_watch.c
其中,audit.c
、auditsc.c
和 auditfilter.c
是最核心的
本质上 audit_log_start
的工作是建立一个 audit_buffer 实例并将其返回给调用者,但在此之前需要考虑积压队列的长度限制和发送数据的速率限制。
如果积压队列长度和数据速率限制的检查能够通过,则使用 audit_buffer_alloc
分配一个 audit_buffer
实例。
在所有必要的记录消息都已经写入到审计缓冲区之后,需要调用 audit_log_end
确保将审计记录发送给用户空间守护进程。
本文的目的是精挑细选,保留常见命令中的一个最小命令集,只提供其中使用频率最高的用法,原则是如无必要,勿增实体,在足够应对日常工作的前提下,尽可能地减小记忆负担。
例如,删除一个目录,可以使用
rm -rf
,也可以使用rmdir
,后者在这种情况下就是多余的。
你可以通过两种方法阅读本篇文章:
Ctrl+F
搜索需要查询的命令。由于工作原因,笔者暂时无法一次性完成整篇文章,但会坚持至少一天更新一个命令,请大家监督 :-)
如果你对于命令的选取有疑问或有更好的见解,请在文章末尾留言。
man
是 manual 的简称,当用户需要查看某个命令的参数时,只需要执行 man
即可获取相关资料(当然你也可以通过 man man
来查看 man
命令的用法)。
man 手册可分为以下 9 种类型:
举例说明,read
既是系统调用,又是 bash 命令,查看 man 手册的时候需要作区分:
1 | man 1 read # 查看作为命令的read |
如果不确定应该打开哪一类 man 手册,可以先通过 whatis
命令查询关键字:
1 | whatis read |
man 手册通常包括以下章节:
下面是 man 手册的使用方法:
dpkg 是 Debian 类 Linux 发行版的包管理程序,常见用法包括:
1 | dpkg -i [.deb package name] # 安装软件包 |
ln
命令的功能是为文件在另一个位置建立链接,当我们需要在不同目录用到相同的文件时,只要在其中一个目录下存储该文件,然后在其他目录建立对它的链接,从而减少了重复的空间占用。
最简单的身份切换命令,最常见的用法如下:
1 | su - [username] # 切换到username指定的用户,需要输入新用户的密码 |
要完整地切换到新用户的环境,必须使用 su - username
或 su -l username
,才会连同 PATH、USER、MAIL 等变量都转成新用户的环境。
使用 root 切换成其他用户时,不需要输入新用户的密码。
从事任何行业都应当首先找准自己的角色定位,即世界需要什么,以及自身能够贡献何种价值。
操作系统开发的特点:
通过书籍学习的特点是经典,不容易过时。
适合跟踪学术界的最新动态。
适合了解行业发展趋势,尤其是从商业角度。
个人信息源有时候比公共信息源更珍贵,如果你恰好找到了
你最大的责任就是把你这块材料铸造成器。——易卜生
之前讲的都是如何塑造自己成为 “Who I am”,这里要讲的是 “What others think I am”。
待更新。
]]>Hexo 是一个快捷、简单、强大的博客生成框架,用户只需使用 Markdown 或其他标记语言写作,Hexo 将负责自动生成美观的静态网页。
安装 Git:
1 | sudo apt install git |
配置 Git:
1 | git config --global user.name "你的GitHub用户名" |
安装 Node.js:
1 | curl -sL https://deb.nodesource.com/setup_15.x | sudo -E bash - |
安装 Hexo:
1 | npm install -g hexo-cli |
在根目录下生成 SSH 密钥对:
1 | ssh-keygen -t -rsa "你的GitHub注册邮箱" |
然后复制 ~/.ssh/id_rsa.pub
文件的内容,添加到 GitHub 上。检测公钥设置是否成功:
1 | ssh git@github.com |
在 GitHub 上新建一个仓库,名字是“用户名+github.io”,例如我的用户名是 PrinterFrankin,对应的仓库名就是 PrinterFranklin.github.io。
使用 hexo 初始化博客:
1 | mkdir myblog |
测试博客:
1 | hexo s --debug |
这个命令会生成博客站点的预览,可以在 localhost:4000
进行访问。如果对文件进行了修改,只需刷新页面就能预览最新的效果。
在博客根目录下执行:
1 | hexo new "新文章标题" |
编辑 source/_post/
目录下新增的 .md 文件,并保存。
如果文章中使用了图片,需要进行如下配置:
安装 hexo-asset-image 插件:
1 | npm install hexo-asset-image --save |
在 _config.yml
配置文件中,修改 post_asset_folder 字段为 true。
之后每通过
hexo new
新建文章的时候都会同步在_post
目录下生成一个同名目录,用于存放文章中引用到的图片。
插入图片时,首先将图片存储在文章同名资源目录下,然后在文章中通过 ![test](test.jpg)
语法引用。
配置 _config.yml
文件,找到 deploy
字段,添加 GitHub 仓库信息:
1 | deploy: |
还需要安装 Hexo 部署插件:
1 | npm install hexo-deployer-git --save |
根据以下命令进行部署:
1 | hexo clean |
接下来还需要等待一段时间完成网站部署,之后博客就能访问啦。
升级 Hexo:
1 | npm update hexo -g |
Hexo 官网的主题页面提供了很多主题供用户挑选:https://hexo.io/themes/ 。
更换一个新的主题,你需要:
themes
目录下创建一个新目录,将新主题的文件放在下面。_config.yml
文件,修改 theme
字段的值为新的主题。我使用的主题是 ILS:https://github.com/XPoet/hexo-theme-keep 。这款主题有效集成了一些实用插件,能够降低部署难度,让你更好地专注于写作本身。
当然,如果对其他主题都不满意的话,你也可以开发一个自己的主题 ;-)
主要有 Valine 和 GitTalk 两种方法,具体可参考主题提供的相关说明。
source/CNAME
文件中。快速部署已经能满足最基本的使用需求,但托管在 Github 上的是静态网页文件,并非源文件,源文件只保存在初始化博客的电脑中,这种方案的缺点在于:
很容易想到把源文件也放到 Github 托管,之后在新电脑上写博客的时候需要:
git pull
更新博客源文件;这样做显然很麻烦,那么有没有一种方案,可以只往源文件 git push
,就能自动执行 hexo g & hexo d
完成静态网页的生成和博客的部署呢?
这里使用的是基于 Github Actions 的方案,不阐述过多的基础知识,需要了解背景的读者可以参考阮一峰的 GitHub Actions 入门教程。
注:常用的 CI/CD 工具都可以满足自动部署需求,你也可以使用更老牌的 Travis CI:使用 Travis CI 自动部署 Hexo 静态博客 。但这种方案有个缺点,那就是源文件必须存放在公共仓库(私有仓库使用 Travis CI 要收费),可能造成一定的安全风险。
在 Github 上新建一个源文件仓 hexo-source,设置成私有仓库。
注:源文件必须存放在私有仓库,以避免 App Key 等敏感信息泄露。
生成一对密钥:
1 | ssh-keygen -t rsa -f github-deploy-key |
把私钥 ~/.ssh/github-deploy-key
的内容放到源文件仓 hexo-source 的 Settings -> Secrets
目录下,命名为 HEXO_DEPLOY_PRI
。
把公钥 ~/.ssh/github-deploy-key.pub
的内容放到静态文件仓 xxx.github.io 的 Settings -> Deploy keys
目录下,命名为 HEXO_DEPLOY_PUB
。
注:一定要勾选
Allow write access
!否则会因为无法写入会导致部署失败。
在博客目录下新建 .github/workflows/deploy.yml
文件,内容参考如下:
1 | name: CI |
该文件就是 Github Actions 脚本,你需要修改的只有 GIT_USER
、GIT_EMAIL
和 DEPLOY_REPO
。
上传博客目录到 Github 源码仓:
1 | git init |
如果你使用
git clone
方式下载主题的源码,建议删除主题目录下的 .git 文件。
等 Actions 脚本跑完后,就能在网站上看到更新的博客啦。
每次更新后需要手动刷新 GitHub 个性域名?
答:在 source
目录下添加一个新文件 CNAME,内容是自定义域名。
哪里能获取到更多关于 Hexo 的信息?
答:推荐 Easy Hexo 团队提供的轻松入门 Hexo。
启动自己的开源项目后,除了为项目选择一个合适的开源许可证(License),一份规范的贡献者协议通常也是不可或缺的。事实上,几乎所有大型的开源项目,诸如 Apache、Kubernetes 等,都要求在提交 PR 前签署贡献者协议。
你可能会问,License 不够用吗?为什么还要加一个贡献者协议呢?这就得从著作权说起了。
从原理上说,软件开源开放的是软件的使用权,而非著作权(版权)。开源许可证指明了用户在某些限制下使用软件及其源代码,如果用户违反了开源许可证,最终依然要回归到著作权的法律框架下解决争端。
那么著作权是怎么来的呢?根据《著作权法》的规定,著作权是作品完成时自然产生的,归作品的作者所有。对于大多数开源项目来说,著作权并不属于单一的人或实体,而是所有贡献者都有一份。可以想象,如果发生开源软件侵权,著作权的分散将给维权带来很多麻烦。另外,假如项目所有者想要更换或调整开源许可证,会因为并不持有全部的著作权受到阻碍,还可能由于著作权的原因与贡献者产生潜在的争议。
贡献者协议就是为了解决这些问题而生的:
将代码的版权统一授予项目所有者,方便项目的管理和维权。
提供保证和免责声明,避免潜在的法律风险。
设想这样一种场景,Alice 为项目贡献了实际为 Bob 所有的代码,并且未获得 Bob 的授权,但 Alice 在提交代码前签署了贡献者协议,保证代码来自于个人原创,此时法律责任完全由 Alice 承担。
具体到形式上,贡献者协议又可以分为 CLA 和 DCO,两者在不同场景下各有优劣。
CLA,全称 Contributor License Agreement(贡献者许可协议),简单来说就是项目接收贡献者提交的 PR 之前,需要贡献者签署的一份协议,协议只需签署一次,对该贡献者的所有提交都生效。
CLA 由项目所有方自行定义,在细节上有大大小小的差异,没有统一的标准,但大致包括以下内容:
有些项目的 CLA 会相对宽松,例如 Apache 基金会的 CLA 允许贡献者持有著作权,只要求授予项目方分发展示复制等权利。
DCO,全称 Developer Certificate of Origin(开发者原创证书),最初是在 Linux kernel 项目中引入的,由 Linux 基金会于 2004 年制定。
相比于 CLA,DCO 是一种更轻量化的机制,它最大的优点是标准化,不要求开发者阅读冗长的法律条文,只需在提交的时候签署邮件地址即可。DCO 被很好地集成在了 kernel 的版本控制软件 git 里,因此只要在 git commit
的时候添加 -s
选项,Signed-off-by
就能很简单地添加到 commit log 中。
DCO 目前最新版本是 1.1,内容如下:
- 该贡献全部或部分由我创建,我有权根据文件中指明的开源许可提交;或者:
- 该贡献是基于以前的工作,这些工作基于适当的开源许可,无论这些工作全部还是部分由我完成,我有权根据相同的开源许可证(除非我被允许根据不同的许可证提交)提交修改后的工作;或者:
- 该贡献由1、2、或 3 认证的其他人直接提供给我,而我没有对其进行修改。
- 我理解并同意该项目和贡献是公开的,并且该贡献的记录(包括我随之提交的所有个人信息,包括我的签字)将无限期保留,并且可以与本项目或涉及的开源许可保持一致或者重新分配。
协议的核心在于“原创性确认”,也就是让补丁的提交者确认提交内容是自己创作或者经过别人授权的,并且充分了解项目方会如何使用自己的代码。
正是由于 DCO 保留了 CLA 中的核心免责信息,并且具有轻量化特点,越来越多的开源项目,比如 Chef 和 GitLab 选择从 CLA 切换到了 DCO。
两种协议的对比如下:
属性 | CLA | DCO |
---|---|---|
签署方式 | 一次性签署 | 每次提交时追加 Signed-off-by 信息 |
法律责任 | 明确法律义务 | 无声明,用来限制提交者遵守开源 LICENSE |
自定义 | 公司或组织可自行定义 | 不可自定义,内容固定 |
社区属性 | 弱 | 强 |
公司属性 | 强,可签署公司级别的 CLA | 弱 |
使用案例 | Google、CNCF、Alibaba、openEuler | GitLab、Chef、Apache SkyWalking |
说了这么多,究竟应该选择 CLA 还是 DCO 呢?这取决于项目本身和社区的需求,通常来说:
下面以 RT-Thread 社区 CLA 为例,介绍如何编写一份规范的 CLA,如果没有特殊的需求,读者也可以直接照搬。
开头首先明确 CLA 的生效条件和作用范围:
通过签署贡献协议(“本协议”),签署的“贡献者”同意接受“本协议”并受“本协议”约束。“本协议”适用于“贡献者”提交给 xxx 社区 (“社区”)的全部项目(后称“项目”)的“贡献”,无论“贡献”是在签署日期之前,签署时还是之后提供。
接下来对协议中用到的名词进行准确定义:
“贡献”是指受版权法保护的,由“贡献者”有意“提交”以包含在“项目”所分发软件中任何作品。“提交”是指以电子,口头或书面交流的任何形式送给“社区”管理方或其代表,包括但不限于“社区”管理方为管理的为讨论和改进项目所提供的电子邮件列表上的交流,源代码控制系统以及由“社区”管理方或其代表管理的问题跟踪系统,但不包括由“我”明确标记或以书面形式指定为“非贡献”的交流。
“贡献者”或“我”是指下面签名栏中标明的个人或法人实体。对于法人实体,做出“贡献”的实体以及由该实体控制、受其控制或受其共同控制的所有其他实体均被视为“贡献者”。就本定义而言,“控制”是指有受控方或共同受控方至少50%直接或间接的投票权,资金或其他有价证券。
核心部分是贡献者对社区授予版权和专利许可:
“贡献者”授予“社区”管理方和由“项目”所分发的软件的每个接收者一个永久性的、全球性的、免费的、非独占的、不可撤销的、有分许可权的版权许可,供其复制、使用、修改、分发其“贡献”,不论修改与否。
“贡献者”授予“社区”管理方和由“项目”所分发的软件的每个接收者一个永久性的、全球性的、免费的、非独占的、不可撤销的、有分许可权的专利许可,供其制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与“提交”“贡献”时所针对的“项目”软件的结合而将必然会侵犯的专利权利要求,不包括仅因“贡献者”之外的人修改“贡献”或其他结合而将必然会侵犯到的专利权利要求。如由“项目”所分发的软件实际采用的许可证对软件接收者的专利授权有进一步限制规定的,如限制接收者对“贡献”或前述软件发起专利诉讼或其他维权等,则对前述软件接收者的专利授权以具体项目许可证的对应规定为准。
最后是贡献者的保证和免责声明:
“贡献者”保证“我”是“贡献”的版权所有者,或者“我”经版权所有者授权进行“贡献”,并且“我”在法律上授予“本协议”规定的权利。
“贡献者”保证“我”不知晓“我”的任何“贡献”已经侵犯或将侵犯任何第三方的版权,商标,专利或其他知识产权。
除“本协议”明确约定外,“贡献者”的 “贡献”在提供时不带任何明示或默示的担保,“贡献者”或版权所有者不对任何人因使用“项目”所分发的软件或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。
到此为止,一份完整的 CLA 就大功告成啦,通常协议下方还会有表单用于收集贡献者的姓名和邮箱,以便在提交 PR 时检查贡献者是否签署了 CLA。
很多 FOSS 项目(Free and Open-Source Software)在开始时并没有要求贡献者签署 CLA,但伴随着项目参与者越来越多,会有签署 CLA 以及更改 License 的需求,此时可能面临一些困难,例如无法找到过去的贡献者重新签署 CLA。为了避免被起诉的风险,通常会采用两种策略:
所以如果你已经决定要开源自己的代码,并且想要把它做成一个参与者众的大型项目,建议提前考虑好是否要添加贡献者协议,因为越晚考虑的成本可能会越高。
虽然 CLA 在开源软件中得到了广泛运用,但关于它的争议一直没有停歇过。
反对者认为 CLA 与开源运动的初衷相违背,多数情况下签署 CLA 即意味着放弃自己劳动成果的版权(或者说著作权),一旦版权转移给了项目方,项目方就有权在未来更换其他更严格的许可证,甚至直接将项目闭源,这显然是贡献者不愿意看到的。
除此以外,CLA 的非标准性可能导致贡献者和项目方处于地位不对等的状态,贡献者需要在参与每个项目时都仔细检查一遍条款,以避免自己的劳动成果被恶意利用,事实上在繁琐的协议条文面前,拥有成熟法务团队的大公司总是处于主导地位,这与尽可能降低门槛、崇尚自由合作的开源精神不符。
当然也不乏支持 CLA 的声音,理由之一是,CLA 的存在是一种应对法务风险的防御机制,很多大型企业基于开源软件构建产品并对客户提供服务,如果有开源社区的贡献者起诉他们侵犯专利或版权,败诉不仅意味着巨额的赔偿,还可能导致业务中断损失客户。
CLA 让更多大型企业和组织愿意参与开源社区并成为其中的中坚力量,企业在构建产品的过程中反哺开源社区,提高开源软件的代码质量,可以说没有大型企业和组织,也就没有丰富和高质量的开源软件生态,从这点上来说,CLA 也是一种“存在即合理”。
对此,你怎么看呢?
https://en.wikipedia.org/wiki/Contributor_License_Agreement
https://jimmysong.io/blog/open-source-cla/
https://github.com/kubernetes/community/issues/2649
https://opensource.com/article/18/3/cla-vs-dco-whats-difference
http://opensource.guide/zh-hans/
https://www.finnegan.com/en/insights/articles/what-you-should-know-about-contributor-license-agreements-in-open-source-projects.html
https://opensource.stackexchange.com/questions/666/what-can-you-do-if-you-cant-track-down-all-old-contributors-to-sign-a-cla?rq=1
https://opensource.com/article/19/2/cla-problems
https://www.rt-thread.org/cla/
http://disksing.com/cla-and-dco/