#129 Linux 开机自启动

2016-02-09

Linux 系统启动时,内核首先启动用户空间第一个进程 PID 1,即初始化系统(init system)。历史上常见实现包括 SysVinit、Upstart 和 systemd。初始化系统负责根据配置和依赖关系启动系统组件。之后,任何已启动的进程都可以在权限允许的情况下创建新的子进程,因此系统中的程序启动来源并不局限于 init system,例如桌面环境、cron、supervisor 等都可能成为程序启动入口。

Linux 中常见的程序自动启动方式主要包括:

  1. systemd 服务:用于系统服务和后台进程管理;
  2. 桌面环境自启动:用户登录图形桌面后启动应用;
  3. 定时任务:通过 cron 或 systemd timer 按时间启动任务;
  4. 登录脚本:通过 shell profile 等方式在用户登录时执行。

Systemd

PS:截止到 2026 年,除了少数发行版和部分老版本 Linux(如 CentOS 6、Debian 7 及更早版本)使用源自 UNIX System V 的 SysVinit,绝大多数主流 Linux 发行版已经采用 Systemd 作为初始化系统(兼容 SysVinit)。Ubuntu 比较另类的一点是从 6 ~ 14 一直采用的是 Canonical 自研的 Upstart(兼容 SysVinit),最终没有竞争过红帽家的 Systemd,从 15 开始转 Systemd 阵营。
在 Linux 社区,对 Systemd 最主要的批评之一,是它被认为违反了 Unix 的设计哲学——"Do one thing and do it well"。SysVinit 只有几万行代码,本质上只是负责按顺序执行启动脚本,而 Systemd 整个项目的代码规模达到数百万行,除了初始化系统,还集成了日志、登录管理、DNS、网络、定时器等众多功能。因此,不少开发者认为,它已经不仅仅是一个 init 系统,而更像是一个覆盖系统多个基础设施的框架。

-> % cat /etc/systemd/system/multi-user.target.wants/cron.service
[Unit]
Description=Regular background program processing daemon
Documentation=man:cron(8)
After=remote-fs.target nss-user-lookup.target

[Service]
EnvironmentFile=-/etc/default/cron
ExecStart=/usr/sbin/cron -f -P $EXTRA_OPTS
IgnoreSIGPIPE=false
KillMode=process
Restart=on-failure
SyslogFacility=cron

[Install]
WantedBy=multi-user.target
# 查看所有开机启动的服务:
-> % systemctl list-unit-files --state=enabled

# 查看正在运行的服务:
-> % systemctl --type=service --state=running

# 查看某个服务是否开机启动:
-> % systemctl is-enabled cron
enabled

列出 Systemd 定时任务:

-> % systemctl list-timers
NEXT                            LEFT LAST                              PASSED UNIT                           ACTIVATES
Wed 2026-07-15 12:50:00 CST     9min Wed 2026-07-15 12:40:00 CST      56s ago sysstat-collect.timer          sysstat-collect.service
Wed 2026-07-15 13:04:57 CST    23min Wed 2026-07-15 12:32:54 CST     8min ago fwupd-refresh.timer            fwupd-refresh.service
Wed 2026-07-15 13:32:44 CST    51min Wed 2026-07-15 12:34:02 CST     6min ago anacron.timer                  anacron.service
Wed 2026-07-15 14:32:24 CST 1h 51min Wed 2026-07-15 11:32:24 CST  1h 8min ago apport-autoreport.timer        apport-autoreport.service
Wed 2026-07-15 16:23:05 CST 3h 42min Wed 2026-07-15 05:50:18 CST            - motd-news.timer                motd-news.service
Wed 2026-07-15 16:50:48 CST  4h 9min Wed 2026-07-15 10:38:46 CST  2h 2min ago ua-timer.timer                 ua-timer.service
Thu 2026-07-16 00:00:00 CST      11h Wed 2026-07-15 00:00:00 CST            - dpkg-db-backup.timer           dpkg-db-backup.service
Thu 2026-07-16 00:00:00 CST      11h Wed 2026-07-15 00:00:00 CST            - privoxy-cleanup.timer          privoxy-cleanup.service
Thu 2026-07-16 00:00:00 CST      11h -                                      - sysstat-rotate.timer           sysstat-rotate.service
Thu 2026-07-16 00:07:00 CST      11h -                                      - sysstat-summary.timer          sysstat-summary.service
Thu 2026-07-16 00:30:52 CST      11h Wed 2026-07-15 00:29:04 CST            - plocate-updatedb.timer         plocate-updatedb.service
Thu 2026-07-16 00:39:05 CST      11h Wed 2026-07-15 00:29:04 CST            - logrotate.timer                logrotate.service
Thu 2026-07-16 01:57:49 CST      13h Wed 2026-07-15 06:06:45 CST            - apt-daily.timer                apt-daily.service
Thu 2026-07-16 05:50:05 CST      17h Wed 2026-07-15 06:54:49 CST            - man-db.timer                   man-db.service
Thu 2026-07-16 06:07:09 CST      17h Wed 2026-07-15 06:47:46 CST            - apt-daily-upgrade.timer        apt-daily-upgrade.service
Thu 2026-07-16 10:37:23 CST      21h Wed 2026-07-15 10:37:23 CST  2h 3min ago update-notifier-download.timer update-notifier-download.service
Thu 2026-07-16 10:47:22 CST      22h Wed 2026-07-15 10:47:22 CST 1h 53min ago systemd-tmpfiles-clean.timer   systemd-tmpfiles-clean.service
Sat 2026-07-18 06:10:20 CST   2 days Tue 2026-07-07 20:21:04 CST            - update-notifier-motd.timer     update-notifier-motd.service
Sun 2026-07-19 03:10:35 CST   3 days Sun 2026-07-12 03:10:22 CST            - e2scrub_all.timer              e2scrub_all.service
Mon 2026-07-20 00:21:23 CST   4 days Mon 2026-07-13 01:29:24 CST            - fstrim.timer                   fstrim.service

20 timers listed.
Pass --all to see loaded but inactive timers, too.

-> % cat /etc/systemd/system/timers.target.wants/anacron.timer
[Unit]
Description=Trigger anacron every hour

[Timer]
OnCalendar=*-*-* 07..23:30
RandomizedDelaySec=5m
Persistent=true

[Install]
WantedBy=timers.target
  • 单调定时器:OnActiveSec=, OnBootSec=, OnStartupSec=, OnUnitActiveSec=, OnUnitInactiveSec=
  • 日历定时器:OnCalendar 完整格式:

    DayOfWeek Year-Month-Day Hour:Minute:Second
    

更多选项可以参考:金步国 systemd.timer 中文手册

桌面环境自动启动

systemd -> gdm3 (显示管理器) -> gnome-session -> GNOME Session Manager -> 读取 ~/.config/autostart/*.desktop
-> % find ~/.config/autostart -type f
/home/catroll/.config/autostart/com.github.hluk.copyq.desktop
/home/catroll/.config/autostart/remmina-applet.desktop.save
/home/catroll/.config/autostart/remmina-applet.desktop
/home/catroll/.config/autostart/nutstore-daemon.desktop
-> % ps -ef | grep remmina | grep -Fv grep
catroll     5357    4639  0 10:34 ?        00:00:00 /usr/bin/remmina -i
-> % ps -fp 4639
UID          PID    PPID  C STIME TTY          TIME CMD
catroll     4639       1  0 10:34 ?        00:00:00 /usr/lib/systemd/systemd --user

定时任务

cron 这个命令自 Unix 系统开始开发就存在。
后来被广泛采纳的是 Paul Vixie 开发的版本,被称之为 Vixie Cron

  • /etc/crontab
  • /etc/cron.d
  • 支持分钟级调度

配置格式就是大家熟悉的:

  * * * * * <command to execute>
# | | | | |
# | | | | day of the week (0–6) (Sunday to Saturday;
# | | | month (1–12)             7 is also Sunday on some systems)
# | | day of the month (1–31)
# | hour (0–23)
# minute (0–59)

Vixie Cron 有两个影响大的分叉:

  • 红帽系:Cronie

    [root@iZt4nerpnxvn37c1tlx3brZ ~]# yum list --installed | grep cron
    cronie.x86_64                          1.5.7-15.el9                   @System
    cronie-anacron.x86_64                  1.5.7-15.el9                   @System
    crontabs.noarch                        1.11-27.20190603git.el9        @System
    
  • Debian 系:Debian cron

    -> % apt list --installed | grep cron
    anacron/resolute,now 2.3-45ubuntu1 amd64 [installed]
    cron-daemon-common/resolute,now 3.0pl1-200ubuntu1 all [installed,automatic]
    cron/resolute,now 3.0pl1-200ubuntu1 amd64 [installed,automatic]
    

简单使用没有什么太大区别。
值得注意的是,anacron 也是 cronie 项目组维护,所以在红帽系 anacron 的包名是 cronie-anacron。

anacron 也是一个知名的定时任务工具,它不是用来取代 cron,而是作为 cron 的补充机制。
按照 cron 的设计,任务会在预定时间点执行;如果系统在该时间点处于关机状态,任务就会被直接跳过,不会补执行。例如,将备份任务设置为每天凌晨 0 点执行,如果电脑在 23:59 关机、00:01 开机,那么当天的备份就会被错过。
对于服务器而言,这通常不是问题,因为服务器很少关机;但对于个人电脑、笔记本等经常关机或休眠的设备,这种情况十分常见。anacron 的作用就是记录这些周期性任务是否已经执行,如果发现因为关机而错过了执行时机,就会在系统下次启动后适时补执行一次,从而保证每天、每周或每月的维护任务不会遗漏。

anacron 并不支持分钟级调度,它主要面向 daily、weekly、monthly 等周期性任务。
比如:/etc/anacrontab

#period in days   delay in minutes   job-identifier   command
1   5   cron.daily      nice run-parts /etc/cron.daily
7   25  cron.weekly     nice run-parts /etc/cron.weekly
@monthly 45 cron.monthly        nice run-parts /etc/cron.monthly

这里面就是定义了三条补救策略。

参考:Linux 定时任务(cron/crontab)

登录脚本

登录脚本(Login Script)本质上就是 Shell 在用户登录时自动执行的脚本。如果在这些脚本中启动程序,那么程序就会随着登录自动启动。
需要注意的是,登录脚本属于 Shell 的机制,而不是 Linux 内核或 systemd,因此具有以下特点:

  1. 只有启动 Login Shell 时才会执行。
  2. 每次启动 Login Shell 都会执行,因此使用这种方式实现自启动时,需要自行处理程序重复启动的问题。
  3. 不同 Shell(如 Bash、Zsh)使用的登录脚本不同,配置方法略有差异。
  4. 图形登录是否会执行登录脚本,取决于登录管理器(Display Manager)和桌面会话的具体实现,不同发行版和桌面环境可能存在差异。
  5. 没有生命周期管理能力,程序退出后不会自动重启,也无法方便地控制依赖关系和启动顺序。

注意: ~/.bashrc非 Login Shell 的初始化脚本。如果在其中启动程序,那么每打开一个新的 Bash 终端都会再次执行一次,因此通常不适合作为程序自启动的配置文件。

例如使用 Bash:

用户登录
    │
    ▼
bash
    │
    ├── /etc/profile
    ├── ~/.bash_profile
    ├── ~/.bash_login
    ├── ~/.profile
    └── 执行其中的命令

如果这些文件中有:

~/bin/my-agent &

那么登录后就会自动启动 my-agent

# find ~/.*profile* ~/.bash* /etc/profile* -type f | sort -u
/etc/profile
/etc/profile.d/bash_completion.sh
/etc/profile.d/colorgrep.csh
/etc/profile.d/colorgrep.sh
/etc/profile.d/colorls.csh
/etc/profile.d/colorls.sh
/etc/profile.d/colorsysstat.csh
/etc/profile.d/colorsysstat.sh
/etc/profile.d/colorxzgrep.csh
/etc/profile.d/colorxzgrep.sh
/etc/profile.d/colorzgrep.csh
/etc/profile.d/colorzgrep.sh
/etc/profile.d/csh.local
/etc/profile.d/debuginfod.csh
/etc/profile.d/debuginfod.sh
/etc/profile.d/gawk.csh
/etc/profile.d/gawk.sh
/etc/profile.d/lang.csh
/etc/profile.d/lang.sh
/etc/profile.d/less.csh
/etc/profile.d/less.sh
/etc/profile.d/sh.local
/etc/profile.d/which2.csh
/etc/profile.d/which2.sh
/root/.bash_history
/root/.bash_logout
/root/.bash_profile
/root/.bashrc

#128 MySQL 索引

2016-02-08
CREATE [UNIQUE | FULLTEXT | SPATIAL] INDEX index_name
    [index_type]
    ON tbl_name (key_part,...)
    [index_option]
    [algorithm_option | lock_option] ...

key_part:
    col_name [(length)] [ASC | DESC]

index_option: {
    KEY_BLOCK_SIZE [=] value
  | index_type
  | WITH PARSER parser_name
  | COMMENT 'string'
}

index_type:
    USING {BTREE | HASH}

algorithm_option:
    ALGORITHM [=] {DEFAULT | INPLACE | COPY}

lock_option:
    LOCK [=] {DEFAULT | NONE | SHARED | EXCLUSIVE}

功能类型

  • 主键索引(PrimaryKey)
  • 唯一索引(UNIQUE)
  • 普通索引(KEY)
  • 全文索引(FULLTEXT)
  • 空间索引(SPATIAL)

算法类型

  • B-Tree
  • Hash
  • R-TREE(空间索引使用)
  • 倒排索引(Inverted Index,全文索引使用)

聚簇索引 Clustered Index

聚簇索引(Clustered Index)是一种特殊的索引类型,它决定了表中数据的物理存储顺序。
在聚簇索引中,数据行按照索引键的顺序存储在磁盘上,因此具有相邻的物理位置,这样可以提高查询效率。
聚簇索引只能有一个,因为它决定了表中数据的物理存储顺序,如果有多个聚簇索引,就会导致数据在磁盘上存储的位置不确定,影响查询效率。

其他索引叫二级索引(Secondary Index),或者辅助索引。

对于 InnoDB,有限使用主键做聚簇索引,其次找一个不含 NULL 值的唯一索引,还没有,就自动生成一个隐式的自增型 ROW_ID 字段(BIGINT UNSIGNED)做聚簇索引 GEN_CLUST_INDEX。

注意:所有索引在提升查询效率的同时,都会影响插入和删除的性能。尤其是聚簇索引需要重新组织数据行的物理存储顺序

#127 Python tempfile

2016-02-03
import tempfile
import time

timestr = time.strftime("%Y%m%d%H%M%S")

临时文件

# tempfile.mktemp(suffix='', prefix='tmp', dir=None)
# tempfile.mkstemp(suffix=None, prefix=None, dir=None, text=False)

filepath_temp = tempfile.mktemp(suffix='.html', prefix='.cache_%s_' % timestr)
print(filepath_temp)
# /tmp/.cache_20210929205452_zlmi3pkf.html

filepath_temp = tempfile.mktemp(suffix='.html', prefix='.cache_%s_' % timestr,
                                dir='/opt/apps/markjour/tmp/')
print(filepath_temp)
# /opt/apps/markjour/tmp/.cache_20210929205452_mcr7nj3e.html

filepath_temp = tempfile.mkstemp(suffix='.html', prefix='.cache_%s_' % timestr, text=True)

mkstempmktemp 的区别:mkstemp 返回一个文件描述符,mktemp 返回一个文件路径。
mktemp 返回的路径理论上会被另一个进程使用,所以这是 UNSAFE 的,应该用 mkstemp 代替。
mktemp 从 Python 2.3 开始标记为 Deprecated,但是至今还是可以调用。

临时目录

# tempfile.mkdtemp(suffix=None, prefix=None, dir=None)
dirpath_temp = tempfile.mkdtemp()
print(dirpath_temp)
# /tmp/tmp1dj2cl0d

dirpath_temp = tempfile.mkdtemp(prefix='build_',
                                dir='/opt/apps/markjour/tmp/')
print(dirpath_temp)
# /opt/apps/markjour/tmp/build_9k5synh5

#126 Python zipfile

2016-02-02
import zipfile
import os

zip_path = "/path/to/zip/file.zip"
extract_path = "/path/to/extract/directory"

if not os.path.exists(extract_path):
    os.makedirs(extract_path)

with zipfile.ZipFile(zip_path, 'r') as fp:
    # 直接解压
    zip_ref.extractall(target_dir)

    # 逐个解压
    for filename in fp.namelist():

        # 如果文件存在就先移除,避免 FileExistsError
        extract_file_path = os.path.join(extract_path, filename)
        if os.path.exists(extract_file_path):
            # print("跳过解压缩文件:", filename)
            # continue
            os.remove(extract_file_path)

        # 解压,会自动创建目录结构
        fp.extract(filename, extract_path)

#125 Linux 文件描述符

2016-02-01

文件描述符

File Descriptor

“一切皆文件” 设计思想来自早期 Unix,这是现在 Unix/Linux 界的一个非常重要的概念(根据 Plan9 操作系统的相关资料,这一条没有得到彻底的贯彻)。

允许打开的最大文件数

系统限制

cat /proc/sys/fs/file-nr
# 17781 0   9223372036854775807
# 已分配, 已使用, 最大值

cat /proc/sys/fs/file-max
# 9223372036854775807

sudo sysctl -a | grep file
# fs.file-max = 9223372036854775807
# fs.file-nr = 17813    0   9223372036854775807

修改:

echo 100000000 > /proc/sys/fs/file-max
sysctl fs.file-max 100000000

进程限制

# ulimit 是一个 Shell 内建命令
ulimit -a       # 输出所有的相关限制
ulimit -n       # 当前进程的最大打开文件数
ulimit -n 10240 # 修改当前进程的最大打开文件数

ps -ef | grep sshd | grep -Fv 'grep '
root        1080       1  0 10月16 ?      00:00:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups
sudo ls -l /proc/`pgrep sshd`/fd
总用量 0
lr-x------ 1 root root 64 10月 17 12:04 0 -> /dev/null
lrwx------ 1 root root 64 10月 17 12:04 1 -> 'socket:[25487]'
lrwx------ 1 root root 64 10月 17 12:04 2 -> 'socket:[25487]'
lrwx------ 1 root root 64 10月 17 12:04 3 -> 'socket:[28674]'
lrwx------ 1 root root 64 10月 17 12:04 4 -> 'socket:[28676]'

/etc/security/limits.conf

echo "* soft nofile 65535" | sudo tee -a /etc/security/limits.conf
echo "* hard nofile 65535" | sudo tee -a /etc/security/limits.conf

系统会根据 /etc/pam.d/login 中的 PAM 配置来为登录用户设置,
如果配置了 session required /lib/security/pam_limits.so, 就会加载 /etc/security/limits.conf 设置用户的各种限制值。

<domain>        <type>  <item>  <value>
  1. domain
  2. a user name
  3. a group name, with @group syntax
  4. the wildcard *, for default entry
  5. the wildcard %, can be also used with %group syntax, for maxlogin limit
  6. NOTE: group and wildcard limits are not applied to root.
    To apply a limit to the root user, must be the literal username root.
  7. type
  8. "soft" for enforcing the soft limits
  9. "hard" for enforcing hard limits
  10. item
  11. core - limits the core file size (KB)
  12. data - max data size (KB)
  13. fsize - maximum filesize (KB)
  14. memlock - max locked-in-memory address space (KB)
  15. nofile - max number of open file descriptors
  16. rss - max resident set size (KB)
  17. stack - max stack size (KB)
  18. cpu - max CPU time (MIN)
  19. nproc - max number of processes
  20. as - address space limit (KB)
  21. maxlogins - max number of logins for this user
  22. maxsyslogins - max number of logins on the system
  23. priority - the priority to run user process with
  24. locks - max number of file locks the user can hold
  25. sigpending - max number of pending signals
  26. msgqueue - max memory used by POSIX message queues (bytes)
  27. nice - max nice priority allowed to raise to values: [-20, 19]
  28. rtprio - max realtime priority
  29. chroot - change root to directory (Debian-specific)

#124 Git: 远程引用不存在

2016-01-31

删除远程分支时报错:

git push --delete origin new
error: 无法删除 'new':远程引用不存在
error: 无法推送一些引用到 'gitee.com:markjour/django-admin'

如果是英文环境就是报:

git push --delete origin new
error: unable to delete 'new': remote ref does not exist
error: failed to push some refs to 'gitee.com:markjour/django-admin'

一般是这个分支已经被别人删除了。

Solution

git branch -d -r origin/new

#123 转载:SSL, SSH, OpenSSH, OpenSSL 有什么区别

2016-01-30

我仔细阅读原文,然后按照理解对原文做了重新整理。

  1. SSL 是通讯链路的附加层。可以包含很多协议。HTTPS, FTPS, .....

SSL 是一种国际标准的加密及身份认证通信协议,您用的浏览器就支持此协议。
SSL(Secure Sockets Layer)最初是由美国 Netscape 公司研究出来的,后来成为了 Internet 网上安全通讯与交易的标准。
SSL 协议使用通讯双方的客户证书以及 CA 根证书,允许客户/服务器应用以一种不能被偷听的方式通讯,在通讯双方间建立起了一条安全的、可信任的通讯通道。
它具备以下基本特征:信息保密性、信息完整性、相互鉴定。 主要用于提高应用程序之间数据的安全系数。
SSL 协议的整个概念可以被总结为:一个保证任何安装了安全套接字的客户和服务器间事务安全的协议,它涉及所有TC/IP应用程序。

  1. SSH 只是加密的 Shell,最初是用来替代 telnet 的。通过 port forward,也可以让其他协议通过 SSH 的隧道而起到加密的效果。

SSH 的英文全称是 Secure SHell。通过使用SSH,你可以把所有传输的数据进行加密,这样“中间人”这种攻击方式就不可能实现了,而且也能够防止 DNS 和 IP 欺骗。
还有一个额外的好处就是传输的数据是经过压缩的,所以可以加快传输的速度。
SSH 有很多功能,它既可以代替 telnet,又可以为 ftp、pop、甚至 ppp 提供一个安全的“通道”。
SSH 是由客户端和服务端的软件组成的,有两个不兼容的版本分别是:1.x 和 2.x。用 SSH 2.x 的客户程序是不能连接到 SSH 1.x 的服务程序上去的。OpenSSH 2.x 同时支持 SSH 1.x 和 2.x。

SSH 的安全验证是如何工作的从客户端来看,SSH 提供两种级别的安全验证。

  1. 第一种级别(基于口令的安全验证)只要你知道自己帐号和口令,就可以登录到远程主机。
    所有传输的数据都会被加密,但是不能保证你正在连接的服务器就是你想连接的服务器。可能会有别的服务器在冒充真正的服务器,也就是受到“中间人”这种方式的攻击。
  2. 第二种级别(基于密匙的安全验证)需要依靠密匙,也就是你必须为自己创建一对密匙,并把公用密匙放在需要访问的服务器上。
    如果你要连接到 SSH 服务器上,客户端软件就会向服务器发出请求,请求用你的密匙进行安全验证。
    服务器收到请求之后,先在你在该服务器的家目录下寻找你的公用密匙,然后把它和你发送过来的公用密匙进行比较。
    如果两个密匙一致,服务器就用公用密匙加密“质询”(challenge)并把它发送给客户端软件。
    客户端软件收到“质询”之后就可以用你的私人密匙解密再把它发送给服务器。
    用这种方式,你必须知道自己密匙的口令。

与第一种级别相比,第二种级别不需要在网络上传送口令。
第二种级别不仅加密所有传送的数据,而且“中间人”这种攻击方式也是不可能的(因为他没有你的私人密匙)。
但是整个登录的过程可能需要10秒。

  1. OpenSSL 一个 C 语言函数库,是对 SSL 协议的实现。
    OpenSSL 很优秀,所以很多涉及到数据加密、传输加密的地方都会使用 OpenSSL 的库来做。

  2. OpenSSH 是对 SSH 协议的实现。

OpenSSH 利用 OpenSSL 提供的库。OpenSSL 中也有个叫做 openssl 的工具,是 OpenSSL 中的库的命令行接口。
从编译依赖上看,OpenSSH 依赖于 OpenSSL,没有 OpenSSL 的话 OpenSSH 就编译不过去,也运行不了。

  1. HTTPS 可以使用 TLS 或者 SSL 协议,而 OpenSSL 是 TLS、SSL 协议的开源实现,提供开发库和命令行程序。

可以理解成所有的 HTTPS 都使用了 OpenSSL。

脚本

# 查看使用了 libssl 库的程序
sudo grep -l "libssl" /proc/*/maps | tr -cd 0-9\\n | xargs -r ps u

References

  • http://en.wikipedia.org/wiki/Secure_Shell
  • http://baike.baidu.com/view/16147.htm
  • http://www.zhihu.com/question/23341334