Linux【8】-软件管理-1-4-Tarball 的管理与建议

从前面几个小节的说明当中,我们晓得其实Tarball的安装是可以跨平台的,因为C语言的程式码在各个平台上面是可以共通的,只是需要的编译器可能并不相同而已。例如Linux上面用gcc而Windows上面也有相关的C编译器啊~所以呢,同样的一组原始码,既可以在CentOS Linux上面编译,也可以在SuSE Linux上面编译,当然,也可以在大部分的Unix平台上面编译成功的!

如果万一没有编译成功怎么办?很简单啊,透过修改小部分的程式码(通常是因为很小部分的异动而已)就可以进行跨平台的移植了!也就是说,刚刚我们在Linux底下写的程式『理论上,是可以在Windows上面编译的!』这就是原始码的好处啦!所以说,如果朋友们想要学习程式语言的话,鸟哥个人是比较建议学习『具有跨平台能力的程式语言』,例如C就是很不错的一个!

一、使用原始码管理软体所需要的基础软体

从原始码的说明我们晓得要制作一个binary program 需要很多咚咚的呢!这包括底下这些基础的软体:

gcc 或cc 等C 语言编译器(compiler):

没有编译器怎么进行编译的动作?所以C compiler 是一定要有的。不过Linux 上面有众多的编译器,其中当然以GNU 的gcc 是首选的自由软体编译器啰!事实上很多在Linux 平台上面发展的软体的原始码,原本就是以 gcc 为底来设计的呢。

make 及autoconfig 等软体:

一般来说,以Tarball 方式释出的软体当中,为了简化编译的流程,通常都是配合前几个小节提到的 make 这个指令来依据目标档案的相依性而进行编译。但是我们也知道说make 需要 makefile 这个档案的规则,那由于不同的系统里面可能具有的基础软体环境并不相同, 所以就需要侦测使用者的作业环境,好自行建立一个makefile 档案。这个自行侦测的小程式也必须要藉由autoconfig 这个相关的软体来辅助才行。

需要Kernel 提供的Library 以及相关的Include 档案:

从前面的原始码编译过程,我们晓得函式库(library)的重要性,同时也晓得有include档案的存在。很多的软体在发展的时候都是直接取用系统核心提供的函式库与include档案的,这样才可以与这个作业系统相容啊!尤其是在『驱动程式方面的模组』,例如网路卡、音效卡、USB等驱动程式在安装的时候,常常是需要核心提供的相关资讯的。在Red Hat的系统当中(包含Fedora/CentOS等系列) ,这个核心相关的功能通常都是被包含在kernel-source 或kernel-header这些软体名称当中,所以记得要安装这些软体喔!

虽然Tarball 的安装上面相当的简单,如同我们前面几个小节的例子,只要顺着开发商提供的 README 与INSTALL 档案所载明的步骤来进行,安装是很容易的。但是我们却还是常常会在 BBS 或者是新闻群组当中发现这些留言:『我在执行某个程式的侦测档案时,他都会告诉我没有 gcc 这个软体,这是怎么回事?』还有:『我没有办法使用make 耶!这是什么问题?』呵呵!这就是没有安装上面提到的那些基础软体啦!

咦!为什么使用者不安装这些软体啊?这是因为目前的Linux distribution大多已经偏向于桌上型电脑的使用(非伺服器端),他们希望使用者能够按照厂商自己的希望来安装相关的软体即可,所以通常『预设』是没有安装gcc或者是make等软体的。所以啦,如果你希望未来可以自行安装一些以Tarball方式释出的软体时,记得请自行挑选想要安装的软体名称喔!例如在CentOS或者是Red Hat当中记得选择Development Tools 以及Kernel Source Development等相关字眼的软体群集呢。

那万一我已经安装好一部Linux主机,但是使用的是预设值所安装的软体,所以没有make, gcc等咚咚,该如何是好?呵呵!问题其实不大啦,目前使用最广泛的CentOS/Fedora或者是Red Hat大多是以RPM (下一章会介绍)来安装软体的,所以,你只要拿出当初安装Linux时的原版光碟,然后以下一章介绍的RPM来一个一个的加入到你的Linux主机里面就好啦!很简单的啦!尤其现在又有yum这玩意儿,更方便呐!

在CentOS 当中,如果你已经有网路可以连上Internet 的话,那么就可以使用下一章会谈到的yum 啰!透过yum 的软体群组安装功能,你可以这样做:

  • 如果是要安装gcc 等软体发展工具,请使用『 yum groupinstall “Development Tools” 』
  • 若待安装的软体需要图形介面支援,一般还需要『 yum groupinstall “X Software Development” 』
  • 若安装的软体较旧,可能需要『 yum groupinstall “Legacy Software Development” 』

二、Tarball 安装的基本步骤

我们提过以Tarball 方式释出的软体是需要重新编译可执行的 binary program 的。而Tarball 是以tar 这个指令来打包与压缩的档案,所以啦,当然就需要先将Tarball 解压缩,然后到原始码所在的目录下进行makefile 的建立,再以make 来进行编译与安装的动作啊!所以整个安装的基础动作大多是这样的:

  1. 取得原始档:将tarball 档案在/usr/local/src 目录下解压缩;
  2. 取得步骤流程:进入新建立的目录底下,去查阅INSTALL 与README 等相关档案内容(很重要的步骤!);
  3. 相依属性软体安装:根据INSTALL/README 的内容察看并安装好一些相依的软体(非必要);
  4. 建立makefile:以自动侦测程式(configure 或config) 侦测作业环境,并建立Makefile 这个档案;
  5. 编译:以make 这个程式并使用该目录下的Makefile 做为他的参数设定档,来进行make (编译或其他) 的动作;
  6. 安装:以make 这个程式,并以Makefile 这个参数设定档,依据 install 这个标的(target) 的指定来安装到正确的路径!

注意到上面的第二个步骤,通常在每个软体在释出的时候,都会附上INSTALL或者是README这种档名的说明档,这些说明档请『确实详细的』阅读过一遍,通常这些档案会记录这个软体的安装要求、软体的工作项目、与软体的安装参数设定及技巧等,只要仔细的读完这些档案,基本上,要安装好tarball的档案,都不会有什么大问题啰。

至于makefile 在制作出来之后,里头会有相当多的标的(target),最常见的就是install 与clean 啰!通常『make clean』代表着将目标档(object file) 清除掉,『make』则是将原始码进行编译而已。注意喔!编译完成的可执行档与相关的设定档还在原始码所在的目录当中喔!因此,最后要进行『make install』来将编译完成的所有咚咚都给他安装到正确的路径去,这样就可以使用该软体啦

OK!我们底下约略提一下大部分的tarball 软体之安装的指令下达方式:

1. ./configure

这个步骤就是在建立Makefile这个档案啰!通常程式开发者会写一支scripts来检查你的Linux系统、相关的软体属性等等,这个步骤相当的重要,因为未来你的安装资讯都是这一步骤内完成的!另外,这个步骤的相关资讯应该要参考一下该目录下的README或INSTALL相关的档案!

2. make clean

make会读取Makefile中关于clean的工作。这个步骤不一定会有,但是希望执行一下,因为他可以去除目标档案!因为谁也不确定原始码里面到底有没有包含上次编译过的目标档案(*.o)存在,所以当然还是清除一下比较妥当的。至少等一下新编译出来的执行档我们可以确定是使用自己的机器所编译完成的嘛!

3. make

make会依据Makefile当中的预设工作进行编译的行为!编译的工作主要是进行gcc来将原始码编译成为可以被执行的object files ,但是这些object files通常还需要一些函式库之类的link后,才能产生一个完整的执行档!使用make就是要将原始码编译成为可以被执行的可执行档,而这个可执行档会放置在目前所在的目录之下,尚未被安装到预定安装的目录中;

4. make install

通常这就是最后的安装步骤了,make会依据Makefile这个档案里面关于install的项目,将上一个步骤所编译完成的资料给他安装到预定的目录中,就完成安装啦!

请注意,上面的步骤是一步一步来进行的,而其中只要一个步骤无法成功,那么后续的步骤就完全没有办法进行的! 因此,要确定每一的步骤都是成功的才可以!举个例子来说,万一今天你在./configure就不成功了,那么就表示Makefile无法被建立起来,要知道,后面的步骤都是根据Makefile来进行的,既然无法建立Makefile,后续的步骤当然无法成功啰!

另外,如果在make 无法成功的话,那就表示原始档案无法被编译成可执行档,那么make install 主要是将编译完成的档案给他放置到档案系统中的,既然都没有可用的执行档了,怎么进行安装?所以啰,要每一个步骤都正确无误才能往下继续做!此外,如果安装成功, 并且是安装在独立的一个目录中,例如/usr/local/packages 这个目录中好了,那么你就必需手动的将这个软体的man page 给他写入/etc/man_db. conf 里面去。

三、一般Tarball 软体安装的建议事项(如何移除?升级?)

Tarball要在/usr/local/src里面解压缩呢?基本上,在预设的情况下,原本的Linux distribution释出安装的软体大多是在/usr里面的,而使用者自行安装的软体则建议放置在/usr/local里面。这是考量到管理使用者所安装软体的便利性。

怎么说呢?我们晓得几乎每个软体都会提供线上说明的服务,那就是 info 与man 的功能。在预设的情况下, man 会去搜寻/usr/local/man 里面的说明文件, 因此,如果我们将软体安装在/usr/local 底下的话,那么自然安装完成之后, 该软体的说明文件就可以被找到了。此外,如果你所管理的主机其实是由多人共同管理的, 或者是如同学校里面,一部主机是由学生管理的,但是学生总会毕业吧?所以需要进行交接,如果大家都将软体安装在 /usr/local 底下,那么管理上不就显的特别的容易吗!

所以啰,通常我们会建议大家将自己安装的软体放置在/usr/local 下,至于原始码 (Tarball)则建议放置在/usr/local/src (src 为source 的缩写)底下啊

再来,让我们先来看一看Linux distribution预设的安装软体的路径会用到哪些?我们以apache这个软体来说明的话(apache是WWW伺服器软体,详细的资料请参考伺服器架设篇。你的系统不见得有装这个软体):

/etc/httpd
/usr/lib
/usr/bin
/usr/share/man

我们会发现软体的内容大致上是摆在etc, lib, bin, man 等目录当中,分别代表『设定档、函式库、执行档、线上说明档』。 好了,那么你是以tarball来安装时呢?如果是放在预设的/usr/local里面,由于/usr/local原本就预设这几个目录了,所以你的资料就会被放在:

/usr/local/etc
/usr/local/bin
/usr/local/lib
/usr/local/man

但是如果你每个软体都选择在这个预设的路径下安装的话, 那么所有的软体的档案都将放置在这四个目录当中,因此,如果你都安装在这个目录下的话, 那么未来再想要升级或移除的时候,就会比较难以追查档案的来源啰!而如果你在安装的时候选择的是单独的目录,例如我将 apache 安装在/usr/local/apache 当中,那么你的档案目录就会变成:

/usr/local/apache/etc
/usr/local/apache/bin
/usr/local/apache/lib
/usr/local/apache/man

呵呵!单一软体的档案都在同一个目录之下,那么要移除该软体就简单的多了! 只要将该目录移除即可视为该软体已经被移除啰!以上面为例,我想要移除apache只要下达『rm -rf /usr/local/apache』就算移除这个软体啦!当然啰,实际安装的时候还是得视该软体的Makefile里头的install资讯才能知道到底他的安装情况为何的。因为例如sendmail的安装就很麻烦……

这个方式虽然有利于软体的移除,但不晓得你有没有发现,我们在执行某些指令的时候,与该指令是否在PATH 这个环境变数所记录的路径有关,以上面为例,我的/ usr/local/apache/bin 肯定是不在PATH 里面的,所以执行apache 的指令就得要利用绝对路径了,否则就得将这个/usr/local/apache/bin 加入PATH 里面。另外,那个/usr/local/apache/man 也需要加入man page 搜寻的路径当中啊!

除此之外, Tarball在升级的时候也是挺困扰的,怎么说呢?我们还是以apache来说明好了。WWW伺服器为了考虑互动性,所以通常会将PHP+MySQL+Apache一起安装起来(详细的资讯请参考伺服器架设篇) ,果真如此的话,那么每个软体在安装的时候『都有一定的顺序与程序!』因为他们三者之间具有相关性,所以安装时必需要三者同时考虑到他们的函式库与相关的编译参数。

假设今天我只要升级PHP 呢?有的时候因为只有涉及动态函式库的升级,那么我只要升级PHP 即可!其他的部分或许影响不大。但是如果今天PHP 需要重新编译的模组比较多,那么可能会连带的,连Apache 这个程式也需要重新编译过才行!真是有点给他头痛的!没办法啦!使用 tarball 确实有他的优点啦,但是在这方面,确实也有他一定的伤脑筋程度。

由于Tarball 在升级与安装上面具有这些特色,亦即Tarball 在反安装上面具有比较高的难度 (如果你没有好好规划的话~),所以,为了方便Tarball 的管理,通常鸟哥会这样建议使用者:

  1. 最好将tarball 的原始资料解压缩到/usr/local/src 当中;
  2. 安装时,最好安装到/usr/local 这个预设路径下;
  3. 考虑未来的反安装步骤,最好可以将每个软体单独的安装在/usr/local 底下;
  4. 为安装到单独目录的软体之man page加入man path搜寻: 如果你安装的软体放置到/usr/local/software/ ,那么man page搜寻的设定中,可能就得要在/etc/man_db.conf内的40~50行左右处,写入如下的一行:

    MANPATH_MAP /usr/local/software/bin /usr/local/software/man

这样才可以使用man 来查询该软体的线上文件啰!

注:时至今日,老实说,真的不太需要有tarball的安装了!CentOS/Fedora有个RPM补遗计画,就是俗称的EPEL计画,相关网址说明如下: https://fedoraproject.org/wiki/EPEL~一般学界会用到的软体都在里头~除非你要用的软体是专属软体(要钱的)或者是比较冷门的软体,否则都有好心的网友帮我们打包好了啦!^_^

四、一个简单的范例、利用ntp 来示范

读万卷书不如行万里路啊!所以当然我们就来给他测试看看,看你是否真的了解了如何利用Tarball来安装软体呢?我们利用时间伺服器(network time protocol) ntp这个软体来测试安装看看。先请到 http://www.ntp.org/downloads.html 这个目录去下载档案,请下载最新版本的档案即可。或者直接到鸟哥的网站下载2015/06公告释出的稳定版本:

http://linux.vbird.org/linux_basic/0520source/ntp-4.2.8p3.tar.gz

假设我对这个软体的要求是这样的:

  • 假设ntp-4...tar.gz 这个档案放置在/root 这个目录下;
  • 原始码请解开在/usr/local/src 底下;
  • 我要安装到/usr/local/ntp 这个目录中;

那么你可以依照底下的步骤来安装测试看看(如果可以的话,请你不要参考底下的文件资料, 先自行安装过一遍这个软体,然后再来对照一下鸟哥的步骤喔!)。

4.1 解压缩下载的tarball ,并参阅README/INSTALL 档案

[root@study ~]# cd /usr/local/src    <==切换目录 
[root@study src]# tar -zxvf /root/ntp-4.2.8p3.tar.gz   <==解压缩到此目录 
ntp -4.2.8p3/           <==会建立这个目录喔!
ntp-4.2.8p3/CommitLog
....(底下省略).... 
[root@study src]# cd ntp-4.2.8p3 
[root@study ntp-4.2.8p3]# vi INSTALL   <==记得README也要看一下!
#特别看一下28行到54行之间的安装简介!可以了解如何安装的流程喔!

4.2 检查configure 支援参数,并实际建置makefile 规则档

[root@study ntp*]# ./configure --help | more   <==查询可用的参数有哪些
  --prefix=PREFIX install architecture-independent files in PREFIX
  --enable-all-clocks + include all suitable non-PARSE clocks:
  --enable-parse-clocks - include all suitable PARSE clocks:
# 上面列出的是比较重要的,或者是你可能需要的参数功能!

[root@study ntp*]# ./configure --prefix=/usr/local/ntp \ 
> --enable-all-clocks --enable-parse-clocks   <==开始建立makefile
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
....(中间省略).... 
checking for gcc... gcc            <==也有找到gcc编译器了!
....(中间省略).... 
config.status: creating Makefile   <==现在知道这个重要性了吧?
config.status: creating config.h
config.status: creating evconfig-private.h
config.status: executing depfiles commands
config.status: executing libtool commands

一般来说configure设定参数较重要的就是那个–prefix=/path了,–prefix后面接的路径就是『这个软体未来要安装到那个目录去?』如果你没有指定–prefix=/path这个参数,通常预设参数就是/usr/local至于其他的参数意义就得要参考./configure –help了!这个动作完成之后会产生makefile或Makefile这个档案。当然啦,这个侦测检查的过程会显示在萤幕上, 特别留意关于gcc的检查,还有最重要的是最后需要成功的建立起Makefile才行!

4.3 最后开始编译与安装噜!

[root@study ntp*]# make clean; make 
[root@study ntp*]# make check 
[root@study ntp*]# make install

#将资料给他安装在/usr/local/ntp底下 整个动作就这么简单,你完成了吗?完成之后到/usr/local/ntp 你发现了什么?

五、利用patch 更新原始码

我们在本章一开始介绍了为何需要进行软体的升级,这是很重要的喔!那假如我是以Tarball来进行某个软体的安装,那么是否当我要升级这个软体时,就得要下载这个软体的完整全新的Tarball呢?举个例子来说,鸟哥的讨论区http://phorum.vbird.org 这个网址,这个讨论区是以phpBB 这个软体来架设的,而鸟哥的讨论区版本为3.1.4 ,目前(2015 /09)最新释出的版本则是phpbb 3.1.5 。那我是否需要下载全新的phpbb3.1.5.tar.gz这个档案来更新原本的旧程式呢?

事实上,当我们发现一些软体的漏洞,通常是某一段程式码写的不好所致。因此, 所谓的『更新原始码』常常是只有更改部分档案的小部分内容而已。既然如此的话, 那么我们是否可以就那些被更动的档案来进行修改就可以咯?也就是说, 旧版本到新版本间没有更动过的档案就不要理他,仅将有修订过的档案部分来处理即可。

这有什么好处呢?首先,没有更动过的档案的目标档(object file) 根本就不需要重新编译,而且有更动过的档案又可以利用make 来自动update (更新),如此一来,我们原先的设定(makefile 档案里面的规则) 将不需要重新改写或侦测!可以节省很多宝贵的时间呢(例如后续章节会提到的核心的编译!)

从上面的说明当中,我们可以发现,如果可以将旧版的原始码资料改写成新版的版本, 那么就能直接编译了,而不需要将全部的新版 Tarball 重新下载一次呢!可以节省频宽与时间说!那么如何改写原始码?难道要我们一个档案一个档案去参考然后修订吗?当然没有这么没人性!

我们在第十一章、正规表示法的时候有提到一个比对档案的指令,那就是 diff,这个指令可以将『两个档案之间的差异性列出来』呢!那我们也知道新旧版本的档案之间,其实只有修改一些程式码而已,那么我们可以透过diff比对出新旧版本之间的文字差异,然后再以相关的指令来将旧版的档案更新吗?呵呵!当然可以啦!那就是patch 这个指令啦!很多的软体开发商在更新了原始码之后,几乎都会释出所谓的patch file,也就是直接将原始码update而已的一个方式喔!我们底下以一个简单的范例来说明给你了解喔!

这里我们来举个案例解释一下好了。假设我们刚刚计算三角函数的程式(main) 历经多次改版, 0.1 版仅会简单的输出, 0.2 版的输出就会含有角度值,因此这两个版本的内容不相同。如下所示,两个档案的意义为:

http://linux.vbird.org/linux_basic/0520source/main-0.1.tgz :main的0.1版;

http://linux.vbird.org/linux_basic/0520source/main_0.1_to_0.2.patch :main由0.1升级到0.2的patch file;

请您先下载这两个档案,并且解压缩到你的/root 底下。你会发现系统产生一个名为main-0.1 的目录。该目录内含有五个档案,就是刚刚的程式加上一个Makefile 的规则档案。你可以到该目录下去看看Makefile 的内容, 在这一版当中含有main 与clean 两个标的功能而已。至于0.2 版则加入了install 与uninstall 的规则设定。接下来,请看一下我们的作法啰:

测试旧版程式的功能

[root@study ~]# tar -zxvf main-0.1.tgz 
[root@study ~]# cd main-0.1 
[root@study main-0.1]# make clean main 
[root@study main-0.1]# ./main 
version 0.1 
Please input your name: VBird 
Please enter the degree angle (ex> 90): 45
Hi, Dear VBird, nice to meet you.
The Sin is: 0.71
The Cos is: 0.71

与之前的结果非常类似,只是鸟哥将Makefile 直接给您了!但如果你下达make install 时,系统会告知没有install 的target 啊!而且版本是0.1 也告知了。那么如何更新到0.2 版呢?透过这个patch 档案吧!这个档案的内容有点像这样:

查阅patch file 内容

[root@study main-0.1]# vim ~/main_0.1_to_0.2.patch 
diff -Naur main-0.1/cos_value.c main-0.2/cos_value.c
--- main-0.1/cos_value.c 2015-09-04 14:46:59.200444001 +0800
+++ main-0.2/cos_value.c 2015-09-04 14:47:10.215444000 +0800
@@ -7,5 +7,5 @@
 {
        float value;
....(底下省略)....

上面表格内有个底线的部分,那代表使用diff 去比较时,被比较的两个档案所在路径,这个路径非常的重要喔!因为patch 的基本语法如下:

patch -p数字< patch_file

特别留意那个『 -p数字』,那是与patch_file 里面列出的档名有关的资讯。假如在 patch_file 第一行写的是这样:

*** /home/guest/example/expatch.old

那么当我下达『 patch -p0 < patch_file 』时,则更新的档案是『 /home/guest/example/expatch.old 』,如果『 patch -p1 < patch_file』,则更新的档案为『home/guest/ example/expatch.old』,如果『patch -p4 < patch_file』则更新『expatch.old』,也就是说, -pxx那个xx代表『拿掉几个斜线(/)』的意思!这样可以理解了吗?好了,根据刚刚上头的资料,我们可以发现比较的档案是在main-0.1/xxx与main-0.2/xxx ,所以说,如果你是在main-0.1底下,并且想要处理更新时,就得要拿掉一个目录(因为并没有main-0.2的目录存在,我们是在当前的目录进行更新的!),因此使用的是-p1才对喔!所以:

更新原始码,并且重新编译程式!

[root@study main-0.1]# patch -p1 < ../main_0.1_to_0.2.patch
patching file cos_value.c
patching file main.c
patching file Makefile
patching file sin_value.c
#请注意,鸟哥目前所在目录是在main-0.1底下喔!注意与patch档案的相对路径!
# 虽然有五个档案,但其实只有四个档案有修改过喔!上面显示有改过的档案!

[root@study main-0.1]# make clean main 
[root@study main-0.1]# ./main 
version 0.2 
Please input your name: VBird 
Please enter the degree angle (ex> 90): 45
Hi, Dear VBird, nice to meet you.
The sin(45.000000) is: 0.71
The cos(45.000000) is: 0.71
 #你可以发现,输出的结果中版本变了,输出资讯多了括号()喔!

[root@study main-0.1]# make install    <==将他安装到/usr/local/bin给大家用
cp -a main /usr/local/bin
[root@study main-0.1]# main            <==直接输入指令可执行!
[root@study main-0.1]# make uninstall  <==移除此软体!
rm -f /usr/local/bin/main

很有趣的练习吧!所以你只要下载patch file就能够对你的软体原始码更新了!只不过更新了原始码并非软体就更新!你还是得要将该软体进行编译后,才会是最终正确的软体喔!因为patch的功能主要仅只是更新原始码档案而已!切记切记!此外,如果你patch错误呢?没关系的!我们的patch是可以还原的啊!透过『 patch -R < ../main_0.1_to_0.2.patch 』就可以还原啦!很有趣吧!

例题:

如果我有一个很旧版的软体,这个软体已经更新到很新的版本,例如核心,那么我可以使用patch file 来更新吗?

答:

这个问题挺有趣的,首先,你必须要确定旧版本与新版本之间『确实有释出patch file 』才行,以kernel 2.2.xx及2.4.xx来说,这两者基本上的架构已经不同了,所以两者间是无法以patch file来更新的。不过, 2.4.xx与2.4.yy就可以更新了。不过,因为kernel每次推出的patch档案都仅针对前一个版本而已,所以假设要由kernel 2.4.20升级到2.4.26 ,就必须要使用patch 2.4.21, 2.4.22, 2.4.23, 2.4 .24, 2.4.25, 2.4.26六个档案来『依序更新』才行喔!当然,如果有朋友帮你比对过2.4.20与2.4.26 ,那你自然就可以使用该patch file来直接一次更新啰!

参考资料

个人公众号,比较懒,很少更新,可以在上面提问题,如果回复不及时,可发邮件给我: tiehan@sina.cn

Sam avatar
About Sam
专注生物信息 专注转化医学