标签 fork 下的文章

开源软件的发行版和分支是不一样的。了解其中的区别和潜在的风险。

如果你们对开源软件有过一段时间的了解,一定曾在许多相关方面中听说过 分支 fork 发行版 distribution 两个词。许多人对这两个词的区别不太清楚,因此我将试着通过这篇文章为大家解答这一疑惑。

(LCTT 译注:fork 一词,按我们之前的倡议,在版本控制工作流中,为了避免和同一个仓库的 branch 一词混淆,我们建议翻译为“复刻”。但是在项目和发行版这个语境下,没有这个混淆,惯例上还是称之为“分支”。)

首先,一些定义

在解释分支与发行版两者的细微区别与相似之处之前,让我们先给一些相关的重要概念下定义。

开源软件 是指具有以下特点的软件:

  • 在特定的 许可证 限制下,软件供所有人免费分发
  • 在特定的许可证限制下,软件源代码可以供所有人查看与修改

开源软件可以按以下方式 使用

  • 以二进制或者源代码的方式下载,通常是免费的。(例如,Eclipse 开发者环境
  • 作为一个商业公司的产品,有时向用户提供一些服务并以此收费。(例如,红帽产品
  • 嵌入在专有的软件解决方案中。(例如一些智能手机和浏览器用于显示字体的 Freetype 软件

自由开源软件 free and open source software (FOSS)不一定是“零成本”的“ 免费 free ”。自由开源软件仅仅意味着这个软件在遵守软件许可证的前提下可以自由地分发、修改、研究和使用。软件分发者也可能为该软件定价。例如,Linux 可以是 Fedora、Centos、Gentoo 等免费发行版,也可以是付费的发行版,如红帽企业版 Linux(RHEL)、SUSE Linux 企业版(SLES)等。

社区 community 指的是在一个开源项目上协作的团体或个人。任何人或者团体都可以在遵守协议的前提下,通过编写或审查代码/文档/测试套件、管理会议、更新网站等方式为开源项目作出贡献。例如,在 Openhub.net 网站上,我们可以看见政府、非营利性机构、商业公司和教育团队等组织都在 为一些开源项目作出贡献

一个开源 项目 project 是集协作开发、文档和测试的结果。大多数项目都搭建了一个中央仓库用来存储代码、文档、测试文件和目前正在开发的文件。

发行版 distribution 是指开源项目的一份的二进制或源代码的副本。例如,CentOS、Fedora、红帽企业版 Linux(RHEL)、SUSE Linux、Ubuntu 等都是 Linux 项目的发行版。Tectonic、谷歌的 Kubernetes 引擎(GKE)、亚马逊的容器服务和红帽的 OpenShift 都是 Kubernetes 项目的发行版。

开源项目的商业发行版经常被称作 产品 products ,因此,红帽 OpenStack 平台是红帽 OpenStack 的产品,它是 OpenStack 上游项目的一个发行版,并且是百分百开源的。

主干 trunk 是开发开源项目的社区的主要工作流。

开源分支fork是开源项目主干的一个版本,它是分离自主干的独立工作流。

因此,发行版并不等同于分支。发行版是上游项目的一种包装,由厂商提供,经常作为产品进行销售。然而,发行版的核心代码和文档与上游项目的版本保持一致。分支,以及任何基于分支的的发行版,导致代码和文档的版本与上游项目不同。对上游项目进行了分支的用户必须自己来维护分支项目,这意味着他们失去了上游社区协同工作带来的好处。

为了进一步解释软件分支,让我来用动物迁徙作比喻。鲸鱼和海狮从北极迁徙到加利福尼亚和墨西哥;帝王斑蝶从阿拉斯加迁徙到墨西哥;并且北半球的燕子和许多其他鸟类飞翔南方去过冬。成功迁徙的关键因素在于,团队中的所有动物团结一致,紧跟领导者,找到食物和庇护所,并且不会迷路。

独立前行带来的风险

一只鸟、帝王蝶或者鲸鱼一旦掉队就失去了许多优势,例如团队带来的保护,以及知道哪儿有食物、庇护所和目的地。

相似地,从上游版本获取分支并且独立维护的用户和组织也存在以下风险:

  1. 由于代码不同,分支用户不能够基于上游版本更新代码。 这就是大家熟知的技术债,对分支的代码修改的越多,将这一分支重新归入上游项目需要花费的时间和金钱成本就越高。
  2. 分支用户有可能运行不太安全的代码。 由于代码不同的原因,当开源代码的漏洞被找到,并且被上游社区修复时,分支版本的代码可能无法从这次修复中受益。
  3. 分支用户可能不会从新特性中获益。 拥有众多组织和个人支持的上游版本,将会创建许多符合所有上游项目用户利益的新特性。如果一个组织从上游分支,由于代码不同,它们可能无法纳入新的功能。
  4. 它们可能无法和其他软件包整合在一起。 开源项目很少是作为单一实体开发的;相反地,它们经常被与其他项目打包在一起构成一套解决方案。分支代码可能无法与其他项目整合,因为分支代码的开发者没有与上游的其他参与者们合作。
  5. 它们可能不会得到硬件平台认证。 软件包通常被搭载在硬件平台上进行认证,如果有问题发生,硬件与软件工作人员可以合作找出并解决问题发生的根源。

总之,开源发行版只是一个来自上游的、多组织协同开发的、由供应商销售与支持的打包集合。分支是一个开源项目的独立开发工作流,有可能无法从上游社区协同工作的结果中受益。


via: https://opensource.com/article/18/7/forks-vs-distributions

作者:Jonathan Gershater 选题:lujun9972 译者:Wlzzzz-del 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

本文是关于 fork 和 exec 是如何在 Unix 上工作的。你或许已经知道,也有人还不知道。几年前当我了解到这些时,我惊叹不已。

我们要做的是启动一个进程。我们已经在博客上讨论了很多关于系统调用的问题,每当你启动一个进程或者打开一个文件,这都是一个系统调用。所以你可能会认为有这样的系统调用:

start_process(["ls", "-l", "my_cool_directory"])

这是一个合理的想法,显然这是它在 DOS 或 Windows 中的工作原理。我想说的是,这并不是 Linux 上的工作原理。但是,我查阅了文档,确实有一个 posix\_spawn 的系统调用基本上是这样做的,不过这不在本文的讨论范围内。

fork 和 exec

Linux 上的 posix_spawn 是通过两个系统调用实现的,分别是 forkexec(实际上是 execve),这些都是人们常常使用的。尽管在 OS X 上,人们使用 posix_spawn,而 forkexec 是不提倡的,但我们将讨论的是 Linux。

Linux 中的每个进程都存在于“进程树”中。你可以通过运行 pstree 命令查看进程树。树的根是 init,进程号是 1。每个进程(init 除外)都有一个父进程,一个进程都可以有很多子进程。

所以,假设我要启动一个名为 ls 的进程来列出一个目录。我是不是只要发起一个进程 ls 就好了呢?不是的。

我要做的是,创建一个子进程,这个子进程是我(me)本身的一个克隆,然后这个子进程的“脑子”被吃掉了,变成 ls

开始是这样的:

my parent
    |- me

然后运行 fork(),生成一个子进程,是我(me)自己的一份克隆:

my parent
    |- me
       |-- clone of me

然后我让该子进程运行 exec("ls"),变成这样:

my parent
    |- me
       |-- ls

当 ls 命令结束后,我几乎又变回了我自己:

my parent
    |- me
       |-- ls (zombie)

在这时 ls 其实是一个僵尸进程。这意味着它已经死了,但它还在等我,以防我需要检查它的返回值(使用 wait 系统调用)。一旦我获得了它的返回值,我将再次恢复独自一人的状态。

my parent
    |- me

fork 和 exec 的代码实现

如果你要编写一个 shell,这是你必须做的一个练习(这是一个非常有趣和有启发性的项目。Kamal 在 Github 上有一个很棒的研讨会:https://github.com/kamalmarhubi/shell-workshop)。

事实证明,有了 C 或 Python 的技能,你可以在几个小时内编写一个非常简单的 shell,像 bash 一样。(至少如果你旁边能有个人多少懂一点,如果没有的话用时会久一点。)我已经完成啦,真的很棒。

这就是 forkexec 在程序中的实现。我写了一段 C 的伪代码。请记住,fork 也可能会失败哦。

int pid = fork();
// 我要分身啦
// “我”是谁呢?可能是子进程也可能是父进程
if (pid == 0) {
    // 我现在是子进程
    // “ls” 吃掉了我脑子,然后变成一个完全不一样的进程
    exec(["ls"])
} else if (pid == -1) {
    // 天啊,fork 失败了,简直是灾难!
} else {
    // 我是父进程耶
    // 继续做一个酷酷的美男子吧
    // 需要的话,我可以等待子进程结束
}

上文提到的“脑子被吃掉”是什么意思呢?

进程有很多属性:

  • 打开的文件(包括打开的网络连接)
  • 环境变量
  • 信号处理程序(在程序上运行 Ctrl + C 时会发生什么?)
  • 内存(你的“地址空间”)
  • 寄存器
  • 可执行文件(/proc/$pid/exe
  • cgroups 和命名空间(与 Linux 容器相关)
  • 当前的工作目录
  • 运行程序的用户
  • 其他我还没想到的

当你运行 execve 并让另一个程序吃掉你的脑子的时候,实际上几乎所有东西都是相同的! 你们有相同的环境变量、信号处理程序和打开的文件等等。

唯一改变的是,内存、寄存器以及正在运行的程序,这可是件大事。

为何 fork 并非那么耗费资源(写入时复制)

你可能会问:“如果我有一个使用了 2GB 内存的进程,这是否意味着每次我启动一个子进程,所有 2 GB 的内存都要被复制一次?这听起来要耗费很多资源!”

事实上,Linux 为 fork() 调用实现了 写时复制 copy on write ,对于新进程的 2GB 内存来说,就像是“看看旧的进程就好了,是一样的!”。然后,当如果任一进程试图写入内存,此时系统才真正地复制一个内存的副本给该进程。如果两个进程的内存是相同的,就不需要复制了。

为什么你需要知道这么多

你可能会说,好吧,这些细节听起来很厉害,但为什么这么重要?关于信号处理程序或环境变量的细节会被继承吗?这对我的日常编程有什么实际影响呢?

有可能哦!比如说,在 Kamal 的博客上有一个很有意思的 bug。它讨论了 Python 如何使信号处理程序忽略了 SIGPIPE。也就是说,如果你从 Python 里运行一个程序,默认情况下它会忽略 SIGPIPE!这意味着,程序从 Python 脚本和从 shell 启动的表现会有所不同。在这种情况下,它会造成一个奇怪的问题。

所以,你的程序的环境(环境变量、信号处理程序等)可能很重要,都是从父进程继承来的。知道这些,在调试时是很有用的。


via: https://jvns.ca/blog/2016/10/04/exec-will-eat-your-brain/

作者:Julia Evans 译者:jessie-pang 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

使用过 GitHub 的人大多知道它上面有个“Fork”的功能,用来将某个仓库克隆到你的账户之下,从而可以对其进行修改、衍生,也可以比较方便的将你的修改推回到原来的仓库(所谓的上游)。

随着 GitHub 的流行,我们经常能在各种文章中见到,“fork 某某项目”等说法,是的,“fork”这个一直没有一个正式的译名。

其实这个问题不独出现在 GitHub 中,fork 这个词更早的出现在 Unix/Linux 中的 C 语言编程之中。在 Unix/Linux 的进程模型中,fork 是指进程创建自身副本的操作,它通常是一个在内核中实现的系统调用。fork 是 Unix 类系统中进程创建的主要方式(历史上曾经是唯一的方式)。从那个时候起,fork 就一直没有一个确定的译名。

不过,我们认为,对于这样的一个经常使用的专业名词,有一个明确的译名比较适合,虽然大家都习惯了直接用 fork 一词。

fork 一词在英文中的原意是“叉子”, 虽然翻译成“分叉”、“分支”似乎也可以,但是前者较少用做动词,后者则和 Branch 的译名重复混淆。

据 Linux 中国翻译组(LCTT)的译者 dongfengweixiao 提议,可译作“复刻”,我们认为这是一个可取的译法,一方面照顾到了音译,另一方面其释义也形象直观。

补充 1,“复刻”这样的译法,在网络上已存在,包括中文维基)中也采用该译法,只是并未得到公认和流行。

补充 2,据 @爱开源魅影 称,git 软件包里面,蒋新将“fork”译为“派生”。似也可取。

既然说到这里,顺便我们对 复刻 fork 做一个技术方面的简介吧。

复刻 fork GitHub 仓库

在 GitHub 上评价一个项目(仓库)是否流行,其中一个重要指标就是其 复刻 fork 数。

在 GitHub 上参与一个开源项目的程度有三个阶段:

  • star(点赞),如果你觉得某个项目不错,可以为其点赞(star)
  • watch(关注),如果你希望进一步关注这个项目的进展,可以关注(watch)它
  • fork(复刻),如果你还想进一步为这个项目做一些贡献,可以复刻(fork)它到你自己的账户下,做出了修改之后通过 Pull-Request(PR)方式将你的改动推回给原仓库(上游),如果被接纳就会进入到原仓库之中

显然,一个项目的复刻数越高,代表着贡献者越多。

通过 复刻 fork + PR 的方式对开源项目进行贡献的流程类似下图:

我们知道 GitHub 是运行在 Git 之上的,GitHub 中的 复刻 fork 其本质上是 Git 中的 克隆 clone 。关于 GitHub 中的复刻的进一步介绍,可以参考“在 Github 和 Git 上 fork 之简单指南”一文。

顺便说一句,我们的 LCTT 翻译组就是通过 复刻 fork + PR 的方式运作的,这也是 GitHub 上绝大多数开源项目的运作方式。

复刻 fork 子进程

在 Unix 下的 C 语言编程中,通过 fork() 系统调用来对进程本身进行复制,然后被复制出来的子进程就可以执行不同于父进程的操作,或通过 exec() 运行其它进程。典型的 C 代码如下:

fpid = fork();   

if (fpid < 0)   
    printf("error in fork!");   
else if (fpid == 0) {  
    printf("i am the child process, my process id is %d/n",getpid());   
} else {  
    printf("i am the parent process, my process id is %d/n",getpid());   
}

所有的服务器守护进程,包括你所见到的 Web 服务、MySQL 数据库服务等,都是通过这种方式来产生子进程来提供服务的。甚至,整个 Linux/Unix 中的进程,除了 init 进程本身之外,都是由 init 进程 复刻 fork 出来的。关于服务器编程方面的 复刻 fork 的使用,可以进一步参阅“搭个 Web 服务器(三)”一文。

复刻 fork 炸弹

其实,不只是 C 语言里面有 复刻 fork 的功能,在 shell 里面也有,想必大家可能都听说过 “fork 炸弹”,这就是利用函数的迭代执行,无限 复刻 fork 出许多子进程,从而耗尽系统资源,导致系统崩溃的一个恶意(玩笑)用法。

复刻炸弹有很多种形式,不过最简洁的可能就是如上图的这个了,关于这个炸弹的具体解释,可以参阅“经典的 Fork 炸弹解析”,在此就不赘述了。

如果你对 fork 的翻译有不同的意见,欢迎留言评论。

以我的经验来看,刚接触Git和GitHub时,最困扰的一件事情就是尝试解决下面的问题:在Git和GitHub上,我能做什么?

Git教程往往不会解决这个问题,因为它集中篇幅来教你Git命令和概念,并且不认为你会使用GitHub。GitHub帮助教程一定程度上弥补了这一缺陷,但是它每篇文章的关注点都较为狭隘,而且没有提供关于"Git vs GitHub"问题的概念性概述。

如果你是习惯于先理解概念,再着手代码的学习者,而且你也是Git和GitHub的初学者,我建议你先理解清楚什么是fork。为什么呢 ?

  1. Fork是在GitHub起步最普遍的方式。
  2. Fork只需要很少的Git命令,但是起得作用却非常大。
  3. Fork提供了对Git和GitHub最基础的了解,有益于你之后的工作。

本篇指南使用两张简单的图表,来教会你fork的两种主要工作流程。我并不打算涉及任何代码,但是在结论中,我会把你需要使用的代码的链接给你。

fork并且更新一个仓库

现在有这样一种情形:有一个叫做Joe的程序猿写了一个游戏程序,而你可能要去改进它。并且Joe将他的代码放在了GitHub仓库上。下面是你要做的事情:

Alt text

fork并且更新GitHub仓库的图表演示

  1. Fork他的仓库:这是GitHub操作,这个操作会复制Joe的仓库(包括文件,提交历史,issues,和其余一些东西)。复制后的仓库在你自己的GitHub帐号下。目前,你本地计算机对这个仓库没有任何操作。
  2. Clone你的仓库:这是Git操作。使用该操作让你发送"请给我发一份我仓库的复制文件"的命令给GitHub。现在这个仓库就会存储在你本地计算机上。
  3. 更新某些文件:现在,你可以在任何程序或者环境下更新仓库里的文件。
  4. 提交你的更改:这是Git操作。使用该操作让你发送"记录我的更改"的命令至GitHub。此操作只在你的本地计算机上完成。
  5. 将你的更改push到你的GitHub仓库:这是Git操作。使用该操作让你发送"这是我的修改"的信息给GitHub。Push操作不会自动完成,所以直到你做了push操作,GitHub才知道你的提交。
  6. 给Joe发送一个pull request:如果你认为Joe会接受你的修改,你就可以给他发送一个pull request。这是GitHub操作,使用此操作可以帮助你和Joe交流你的修改,并且询问Joe是否愿意接受你的"pull request",当然,接不接受完全取决于他自己。

如果Joe接受了你的pull request,他将把那些修改拉到自己的仓库。胜利!

同步一个fork

Joe和其余贡献者已经对这个项目做了一些修改,而你将在他们的修改的基础上,还要再做一些修改。在你开始之前,你最好"同步你的fork",以确保在最新的复制版本里工作。下面是你要做的:

Alt text

同步GitHub fork的图表示意图

  1. 从Joe的仓库中取出那些变化的文件:这是Git操作,使用该命令让你可以从Joe的仓库获取最新的文件。
  2. 将这些修改合并到你自己的仓库:这是Git操作,使用该命令使得那些修改更新到你的本地计算机(那些修改暂时存放在一个"分支"中)。记住:步骤1和2经常结合为一个命令使用,合并后的Git命令叫做"pull"。
  3. 将那些修改更新推送到你的GitHub仓库(可选):记住,你本地计算机不会自动更新你的GitHub仓库。所以,唯一更新GitHub仓库的办法就是将那些修改推送上去。你可以在步骤2完成后立即执行push,也可以等到你做了自己的一些修改,并已经本地提交后再执行推送操作。

比较一下fork和同步工作流程的区别:当你最初fork一个仓库的时候,信息的流向是从Joe的仓库到你的仓库,然后再到你本地计算机。但是最初的过程之后,信息的流向是从Joe的仓库到你的本地计算机,之后再到你的仓库。

结论

我希望这是一篇关于GitHub和Git 的 fork有用概述。现在,你已经理解了那些概念,你将会更容易地在实际中执行你的代码。GitHub关于fork和同步的文章将会给你大部分你需要的代码。

如果你是Git的初学者,而且你很喜欢这种学习方式,那么我极力推荐书籍Pro Git的前两个章节,网上是可以免费查阅的。

如果你喜欢视频学习,我创建了一个11部分的视频系列(总共36分钟),来向初学者介绍Git和GitHub。


via: http://www.dataschool.io/simple-guide-to-forks-in-github-and-git/

作者:Kevin Markham 译者:su-kaiyao 校对:wxy

本文由 LCTT 原创翻译,Linux中国 荣誉推出