1、守护进程 Daemon
1.1 简介
守护进程(daemon)是一类在 unix或其他多任务操作系统 的后台运行的 特殊进程,用于执行特定的系统任务,不会接受电脑用户的直接操控。
通常,守护进程没有任何存在的父进程(即pid=1),且在unix系统进程层级中直接位于init之下。
1.2 dos、windows、mac os 下的守护进程
- 在 dos 环境中,此类应用程序被称为 驻留程序(tsr)。
- 在 windows系统中,由称为 windows 服务 的应用程序来履行守护进程的职责。
- 在原本的mac os系统中,此类应用程序被称为 “extensions”。而作为unix-like的 mac os x有守护进程。(在mac os x中也有“服务”,但他们与windows中类似的程序在概念上完全不相同。)
1.3常见守护进程命令
守护进程程序的名称通常以字母“d”结尾:例如,syslogd就是指管理系统日志的守护进程。
本文末尾列举了 Mac 上的常见守护进程。
1.4 生成
生成守护进程方式:对一个子进程调用fork,然后使其父进程立即终止,使得这个子进程能在init下运行。这种方法通常被称为“脱壳”。
1.5 运行
此类程序会被以进程的形式初始化,很多守护进程在系统引导的时候启动,并且一直运行直到 系统关闭。
另一些只在需要的时候才启动,完成任务后就自动结束。
1.6 常用功能
守护进程为对网络请求,其他硬件活动等进行响应,或其他通过某些任务对其他应用程序的请求进行回应提供支持。
守护进程也能够对其他硬件 进行配置(如在某些linux系统上的devfsd),运行计划任务(例如cron),以及运行其他任务。
2、Mac 守护进程
2.1 Mac 常见守护进程
更多守护进程及功能说明,见文章最下方列举
launchd、kextd、notifyd、diskarbitrationd、configd、syslogd、DirectoryService、distnoted、usbmuxd、securityd、hidd、coreaudiod
2.2 Mac OS X的启动原理:
1) mac固件激活,初始化硬件,加载BootX引导器。
2) BootX加载内核与内核扩展(kext)。
3) 内核启动launchd进程。
4) launchd
根据 /System/Library/LaunchAgents
, /System/Library/LaunchDaemons
, /Library/LaunchDaemons
, Library/LaunchAgents
,~/Library/LaunchAgents
里的plist配置,启动服务守护进程。
launchd配置文件总共有五个路径,在系统开启之初,只加载了/System/Library/LaunchDaemons
和/Library/LaunchDaemons
路径下的plist文件;
另外三个路径下的plist文件在用户login之后才进行。
可以去这五个目录下看看,里面都是 plist 文件。
如果要优化系统,这几个目录是关键。
可以通过 system.log 查看开机日志
创建一个daemons或agent是非常简单的,就是创建一个普通的 二进制可执行文件。然后将自己的属性列表文件(.plist)放置到daemons或agent的配置目录中。
2.2.1 system.log
查看地址:
/var/log/system.log
以下是我今天的部分日志,开机时间是 09:04,可以从这条往下看。
昨晚没关机,所以7点、6点、4点都有日志记录,这里也贴出来。
Jun 13 07:20:10 melissashu com.apple.xpc.launchd[1] (com.clburlison.pinpoint[8677]): Service exited with abnormal code: 1
Jun 13 07:20:16 melissashu xpcproxy[8750]: libcoreservices: _dirhelper_userdir: 529: bootstrap_look_up returned (ipc/send) invalid destination port
Jun 13 07:20:16 melissashu netbiosd[8750]: DEPRECATED USE in libdispatch client: Setting timer interval to 0 requests a 1ns timer, did you mean FOREVER (a one-shot timer)?
Jun 13 09:04:44 melissashu syslogd[46]: ASL Sender Statistics
Jun 13 09:04:48 melissashu com.apple.xpc.launchd[1] (com.apple.imfoundation.IMRemoteURLConnectionAgent): Unknown key for integer: _DirtyJetsamMemoryLimit
Jun 13 09:04:50 melissashu com.apple.xpc.launchd[1] (com.apple.quicklook[8766]): Endpoint has been activated through legacy launch(3) APIs. Please switch to XPC or bootstrap_check_in(): com.apple.quicklook
Jun 13 09:04:54 melissashu com.apple.xpc.launchd[1] (com.apple.imfoundation.IMRemoteURLConnectionAgent): Unknown key for integer: _DirtyJetsamMemoryLimit
Jun 13 09:05:24 --- last message repeated 4 times ---
Jun 13 09:05:28 melissashu timed[94]: settimeofday({0x5b206dd8,0xa3a72}) == 0
Jun 13 09:05:55 melissashu com.apple.MediaLibraryService[6039]: BUG in libdispatch client: kevent[mach_recv] monitored resource vanished before the source cancel handler was invoked
3. 进程管理器 launchd
launchd 是 mac系统下通用的 进程管理器,是mac系统下非常重要的一个进程。
一般来说该进程不允许直接以命令行的形式调用。只能通过其控制管理界面,launchctl 来进行控制。
launchd 是Mac OS下,用于初始化系统环境的关键进程。类似Linux下的init, rc。
launchd主要功能是进程管理。可以理解成是一个常驻在后台的进程,根据用户的配置,来响应特定的系统事件。
launchd 既可以用于系统级别的服务,又可以用于个人用户级别的服务。
3.1 launchd 功能
- 可作为定时器使用,像linux下的 cron
- 可作为目录监听器,监听目录文件的变化
- 可以根据配置启动或者停止服务进程,这个用的最多。
launchd代码已经开源,地址:
http://www.opensource.apple.com/release/mac-os-x-1081/
3.2 launchd的相关配置的存放目录
/system/library/launchagents # mac操作系统提供的用户进程,系统本身的代理程序
/system/library/launchdaemons # mac操作系统本身的守护进程
/library/launchdaemons # 管理员提供的系统守护进程,第三方程序的守护程序
/library/launchagents # 管理员设置的用户进程,第三方程序的代理程序,这个目录通常为空
~/library/launchagents # 用户自有的launch代理程序,是有对应的用户才会执行
另:/library/startupitems
这个目录下也有可能存在开机启动项目的配置
一般我们个人编写的守护进程,都应该放到~/library/launchagents
目录里面。
3.2.1 /System/Library、/Library、~/Library 目录的区别?
/System/Library
目录是存放Apple自己开发的软件。/Library
目录是系统管理员存放的第三方软件。~/Library/
是用户自己存放的第三方软件。
3.2.2 LaunchDaemons 和 LaunchAgents的区别?(launchd 常驻进程)
在launchd的语境中,常驻进程有两种:daemon 和 agent
daemon
也就是我们常说的守护进程,这种一般对所有用户都有相同的行为,响应相同的事件,始终运行于后台,没有前台交互界面。agent
这种是用户级别的服务进程,一般以用户的身份运行。
区别
- LaunchDaemons 是用户 未登陆前就启动 的服务(守护进程)。守护程序,后台服务,通常与用户没有交互。由系统自动启动,不管是否有用户登录系统。Daemons文件夹下得plist是只要开机,可以不用登录就会被加载.
- LaunchAgents 是用户 登陆后启动 的服务(守护进程)。代理程序,是一类特殊的守护程序,只有在用户登录的时候,Agents文件夹下的plist是需要用户登录后,才会加载的。可以和用户有交互,还可以有GUI。
3.3 launchd 执行的工作
launchd 会依次去完成以下的工作
1)根据/System/Library/LaunchDaemons
和/Library/LaunchDaemons
路径下的plist文件,加载系统级守护进程;
2)注册上述守护进程需要的套接字及文件描述符;
3)根据plist文件中的 KeepAlive 键值,启动那些需要在系统周期内一直保持的进程;
4)根据plist文件中的设定,在条件满足时启动进程;
5)关机时,给所有由launchd开启的进程发送 SIGTERM 信号。
这时,我们将log信息中的内容与 /System/Library/LaunchDaemons
路径下的plist 进行对照,发现在系统开启之初的bootlog,blued,mDNSResponder等都能再该路径下找到。
LaunchDaemons路径下的plist指定的进程启动是否存在一定的先后顺序呢?
在launchd依次完成的工作中,可以看到它是先注册套接字和文件描述符,然后才去启动进程,因此plist指定的进程的启动先后顺序并不明确。
3.3.1登录流程:
- 用户的login是由 loginwindow 进程完成的,而 loginwindow 的启动又是由
/System/Library/LaunchDaemons
路径下的com.apple.loginwindow.plist
指定的; - 用户登录之后,launchd才会去加载
/System/Library/LaunchAgents
和/Library/ LaunchAgents
以及~/Library/LaunchAgents
路径下的plist文件,从而根据plist文件的具体设置去启动相应的进程。
在log信息中我们还可以看到这个进程:com.apple.SystemStarter
。
StartupItems 的加载就是由这个进程来完成的。
4、launchctl 命令
- 参考:Mac上,执行定时脚本:launchctl
https://blog.csdn.net/a11101171/article/details/49154535
4.1 简介
launchctl 是 launchd 的控制台(也就是人机交互的终端),用于装载进程、卸载进程并控制 launchd。
Launchctl 其实就是写一个*.plist
的文件,它的作用和linux里的Crontab的作用是一样的。
4.2 简单使用
注意:操作时前面比如带上sudo,不然只能操作当前用户的服务,会出现无法操作一些root用户的服务的问题。系统版本为Mac 10.12。
4.2.1 查看所有服务:
sudo launchctl list
4.2.2 查看服务状态
sudo launchctl list | grep <<Service Name>>
如查看macfee
$ sudo launchctl list | grep com.mcafee.ssm.Eupdate
1606 -13 com.mcafee.ssm.Eupdate
输出具有以下含义:
第一个数字(1606)是进程的PID,如果它正在运行,如果它不运行,它显示一个’ - ‘。
第二个数字(-13)是进程的退出代码,如果它已经完成。如果是负数,则是杀死信号的数量。
第三列(com.mcafee.ssm.Eupdate)是进程名称。
4.2.3 停止服务
sudo launchctl stop <<Service Name>>
4.2.4 开始服务
sudo launchctl start <<Service Name>>
4.2.5 kill 服务
sudo launchctl kill <<Service Name>>
4.2.6 加载一个服务到启动列表
sudo launchctl load -w /System/Library/LaunchDaemons/ssh.plist
4.2.7 卸载一个服务
sudo launchctl unload /System/Library/LaunchDaemons/ssh.plist
4.2.8 更多的用法 launchctl help
4.3 编写服务脚本
如: task_1.sh , 并确保有权限能执行这个脚本
echo 'nihao'
#执行其他脚本
python xxx.py
4.3.2 配置 plist
如 ssh.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!--
卸载 launchctl unload cn.zhong.task.plist
启动 launchctl load cn.zhong.task.plist
-->
<!-- 名称,要全局唯一 -->
<key>Label</key>
<string>cn.zhong.task</string>
<!-- 要运行的程序, 如果省略这个选项,会把ProgramArguments的第一个
元素作为要运行的程序 -->
<key>Program</key>
<string>/Users/zengningzhong/Documents/souche/task/task_1.sh</string>
<!-- 命令, 第一个为命令,其它为参数-->
<key>ProgramArguments</key>
<array>
<string>/Users/zengningzhong/Documents/souche/task/task_1.sh</string>
</array>
<!-- 运行时间-->
<key>StartCalendarInterval</key>
<dict>
<key>Minute</key>
<integer>30</integer>
<key>Hour</key>
<integer>11</integer>
</dict>
<!-- 运行间隔,与StartCalenderInterval使用其一,单位为秒
<key>StartInterval</key>
<integer>2</integer>-->
<key>StandardOutPath</key>
<string>/Users/zengningzhong/Documents/souche/task/task_1.log</string>
<key>StandardErrorPath</key>
<string>/Users/zengningzhong/Documents/souche/task/task_1.err</string>
</dict>
</plist>
这里我们假设添加到 /System/Library/LaunchDaemons
中
4.3.3 plist部分参数说明:
- Label 和 ProgramArguments 必填
- Label:对应的需要保证全局唯一性;
- Program:要运行的程序;
- ProgramArguments:命令语句
- StartCalendarInterval:运行的时间,单个时间点使用dict,多个时间点使用 array
- StartInterval:时间间隔,与StartCalendarInterval使用其一,单位为秒
- StandardInPath、StandardOutPath、StandardErrorPath:标准的输入输出错误文件,这里建议不要使用 .log 作为后缀,会打不开里面的信息。
- 定时启动任务时,如果涉及到网络,但是电脑处于睡眠状态,是执行不了的,这个时候,可以定时的启动屏幕就好了。
更多的参数参见:mac官方文档
Key | Description | Required |
---|---|---|
Label | The name of the job | yes |
ProgramArguments | Strings to pass to the program when it is executed | yes |
UserName | The job will be run as the given user, who may not necessarily be the one who submitted it to launchd. | no |
inetdCompatibility | Indicates that the daemon expects to be run as if it were launched by?inetd | no |
Program | The path to your executable. This key can save the ProgramArguments key for flags and arguments. | no |
onDemand | A?boolean?flag that defines if a job runs continuously or not | no |
RootDirectory | The job will be?chrooted?into another directory | no |
ServiceIPC | Whether the daemon can speak IPC to launchd | no |
WatchPaths | Allows launchd to start a job based on modifications at a file-system path | no |
QueueDirectories | Similar to WatchPath, a queue will only watch an empty directory for new files | no |
StartInterval | Used to schedule a job that runs on a repeating schedule. Specified as the number of seconds to wait between runs. | no |
StartCalendarInterval | Job scheduling. The?syntax?is similar to?cron. | no |
HardResourceLimits | Controls restriction of the resources consumed by any job | no |
LowPriorityIO | Tells the kernel that this task is of a low priority when doing file system I/O | no |
Sockets | An array can be used to specify what socket the daemon will listen on for launch on demand | no |
5、进程优化
launchctl 指令会针对服务设置一个禁用标志,launchd启动时会先检查这个服务是否被禁用,从而确定是否需要启用这个服务。
5.1 禁用服务
5.1.1 方法1
先找到禁用标志文件 /var/db/launchd.db/com.apple.launchd/overrides.plist
,查看你要禁用的服务是否已被禁用了。
有些服务已被禁用,但未列在 overrides.plist 里。此时,你还需要检查这个服务的plist文件Label字段是否已经标记为 Disable。
确认这个服务未禁用后,我们就可以通过调用如下命令,来禁用服务:
sudo launchctl unload plist文件路径
sudo launchctl unload -w plist文件路径
比如,我想禁用spotlight,则输入
sudo launchctl unload /System/Library/LaunchAgents/com.apple.Spotlight.plist
sudo launchctl unload -w /System/Library/LaunchAgents/com.apple.Spotlight.plist
禁用完服务以后,重启Mac OS即可生效。
5.1.2 禁用服务的方法2,一种更有效且暴力的方法(推荐)
先卸载服务
sudo launchctl unload /System/Library/LaunchAgents/com.apple.Spotlight.plist
然后将plist文件mv到其他目录备份。
sudo mv /System/Library/LaunchAgents/com.apple.Spotlight.plist ~/launchd.bak
重启。搞定。
5.2 还原服务:
最后,如果发现服务禁用后,系统或软件出现异常,可以通过如下命令来还原
5.2.1 方法1:
sudo launchctl load -wF plist文件路径
5.2.2 方法2:
将备份的plist文件mv回原来的文件夹。
sudo launchctl load plist文件路径
注意:系统级服务的禁用要异常小心,请在禁用前google,确保你熟知这个服务的作用。否则可能导致系统无法启动。
最安全的做法就是不要去禁用它了。
当然,用户服务我们还是可以放心禁用的,有问题最多再启用呗。
下面是一个作者禁用的服务列表:
/System/Library/LaunchDaemons/com.apple.metadata.mds.plist (禁用spotlight的前提)
/System/Library/LaunchAgents/com.apple.Spotlight.plist (Spotlight)
/Library/LaunchDaemons/com.google.keystone.daemon.plist (Google Software Update)
/Library/LaunchAgents/com.google.keystone.root.agent (Google Software Update)
~/Library/LaunchAgents/com.google.keystone.agent.plist (Google Software Update,用户下的进程不需要加 sudo)
~/Library/LaunchAgents/com.apple.CSConfigDotMacCert-ken.wug\@me.com-SharedServices.Agent.plist (me.com的共享服务)
/System/Library/LaunchDaemons/org.cups.cupsd.plist (打印机)
/System/Library/LaunchDaemons/org.cups.cups-lpd.plist (打印机)
/System/Library/LaunchDaemons/com.apple.blued.plist (蓝牙)
/System/Library/LaunchAgents/com.apple.AirPortBaseStationAgent.plist (apple无线基站,我没有这个设备)
6、备注: macOS 下常见守护进程
/sbin/launchd
系统及用户进程管理器,它是内核装载成功后在OS环境下启动的第一个进程,是Mac OS最重要的进程之一。你无法禁用它。
/usr/libexec/kextd
内核扩展服务,响应内核或用户进程的请求,比如装载或卸载内核扩展或提供内核扩展信息给它们。这是Mac的关键守护进程,请不要去禁用它。/usr/sbin/notifyd
消息服务,这是Mac OS消息系统的组成部分之一。我们知道,操作系统的很多组件需要依赖异步消息来通信,这个服务能保证它们正常工作。请不要去禁用它。/usr/sbin/diskarbitrationd
磁盘仲裁服务,作用是为磁盘卷或其他存储部件进行挂载,取消挂载或弹出(比如光驱和dmg)。最常见的就是USB移动硬盘,MP3,IPHONE,IPAD等。
它的原理是当内核发现有新硬件插入时,内核先识别该硬件,如果能识别,则为硬件装载驱动,并通知 diskarbitrationd 挂载它。取消挂载同理。
如果这个服务被禁用,所有即插即用存储设备都会出现异常。建议不要禁用它。/usr/libexec/configd
保存计算机和系统环境的动态配置信息。为需要用到这些信息的进程提供变更通知。比如网络服务(tcp/ip或wins更新等)。
如果这个服务被禁用,网络和一些需要动态配置信息的组件将会出现异常。建议不要禁用它。/usr/sbin/syslogd
系统日志服务,用于记录系统或软件的消息日志,是系统或软件崩溃时查错的关键途径。某些工具也可能依赖与日志在提供服务。
如果这个服务被禁用,所有的消息日志都将停止记录,并可能导致某些软件工作异常。建议不要禁用它。
/usr/sbin/DirectoryService
目录信息收集中心,它会收集各种目录的用户,用户组,权限和路径信息,并在应用程序需要时,反馈给它们。目录的介质主要是指本地磁 盘,LDAP,Netinfo, Active Directory, NIS, Bonjour/Rendesvous/, AppleTalk, Samba FS(SMB)等。
如果这个服务被禁用,可能会导致部分程序性能降低或出现异常。建议不要禁用它。
/usr/sbin/distnoted
提供分布式的消息服务,类似notifyd,但它主要是处理系统外部的一些消息,比如 itune与iphone, ipad, itouch的连接及消息传递功能。
如果你有使用Apple的即插即用设备,为了保证其功能正常使用,建议你不要禁用它。如果没有apple设备,保险期间,还是留着它吧。
/usr/sbin/ntpd
时间同步服务,负责与time.apple.com同步操作系统的时间。这个是基础功能,请不要禁用它。
usbmuxd
/System/Library/PrivateFrameworks/MobileDevice.framework/Versions/A/Resources/usbmuxd
USB多路传输服务,为iphone和itouch提供原生的传输支持(无需越狱)。如果你没有iphone或ipod touch,可以禁用这个服务。/usr/sbin/securityd
Mac OS安全验证模块,它保存了系统的安全信息,可以仲裁一些加密操作,为软件提供安全验证。系统安全是大事,请不要禁用它。/usr/sbin/mDNSResponder
DNS多播响应器,与DNS服务相关,附属作用是为你搜索局域网里的共享设备。包括mac, windows, ichat, ipad等等,并且会显示在finder的右侧菜单中。
请不要禁用它,否则你的DNS将失效,并导致无法访问网络。/System/Library/CoreServices/loginwindow.app/Contents/MacOS/loginwindow
Mac OS用户登陆进程,展示登陆或注销界面,验证用户密码输入,启动Finder, Dock, 和一切需要启动时启动的第三方应用程序都是这个进程的责任。
请不要禁用它。/usr/sbin/KernelEventAgent
处理文件系统的状态。比如“您的磁盘看上去已经满了,您是否需要删除一些数据,以保证系统正常启动”或“一个服务器已经很久没有响应,您是否想需要重新连接它”等。请不要禁用它。/usr/libexec/hidd
人体学输入设备(HID)支持进程。比如键盘,鼠标,机密狗,蓝牙等。请不要禁用它。/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/CarbonCore.framework/Versions/A/Support/fseventsd
文件系统事件系统,它可以广播文件的创建,删除等事件给Mac OS下的所有应用程序,届时,应用程序可以做出一些应对措施。请不要禁用它。/sbin/dynamic_pager
Mac OS下的虚拟内存。当你的物理内存不够用时,就会使用虚拟内存,有的时候,密钥等一些使用频率不高的信息也会直接从物理内存中移除并存入虚拟内存。在 Unix系统(Mac)下,不论你的物理内存有多大,都不要尝试禁用虚拟内存。因为Unix的内存管理策略是尽可能多地使用内存,再大的内存都回随着时间 慢慢被耗光。autofsd
自动挂载各种网络文件系统。比如NFS, SMB, AFS等。配置文件在/etc/auto_master
和/etc/auto_home
,使用方式详见:
http://commandlinemac.blogspot.com/2009/09/introduction-to-autofs-in-mac-os-x.html
如果你不使用任何网络文件系统,可以禁用这个服务。/System/Library/CoreServices/coreservicesd
核心服务守护进程,禁用它可能导致系统停止运转或崩溃。请不要禁用它。/usr/sbin/coreaudiod
音频服务,提供声音相关的支持。请不要禁用它。WindowServer
System/Library/Frameworks/ApplicationServices.framework/Frameworks/CoreGraphics.framework/Resources/WindowServer
Mac OS的GUI界面系统。负责所有应用程序的窗口显示。请不要禁用它。/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/cvmsServ
OPGL服务进程,用到高级图形API的程序需要用到它。比如游戏,支持滑动或谈出特效的软件。请不要禁用它。
/System/Library/CoreServices/Dock.app/Contents/MacOS/Dock
Mac OS经典的任务栏。请不要禁用它。/System/Library/CoreServices/SystemUIServer.app/Contents/MacOS/SystemUIServer
Mac OS的菜单栏。请不要禁用它。/System/Library/CoreServices/Finder.app/Contents/MacOS/Finder
Mac OS的资源管理器。请不要禁用它。
- /usr/sbin/pboard
剪贴板支持。除非你不想用复制,黏贴。请不要禁用它。
/System/Library/Frameworks/ApplicationServices.framework/Frameworks/ATS.framework/Support/fontd
字体服务进程。请不要禁用它。
/usr/libexec/UserEventAgent**
高级别的系统事件处理器。请不要禁用它。
- `/System/Library/CoreServices/Menu Extras/TextInput.menu/Contents/SharedSupport/TISwitcher.app/Contents/MacOS/TISwitcher**
输入法切换服务。除非你不想使用中文输入法,否则,请不要禁用它。
/usr/libexec/taskgated
task_for_pid是用来帮助某些想控制其他进程的执行的进程实现功能的服务。taskgated会被内核呼叫,用来确认”控制“这个行为是否可以发生。它本身也有权限验证的功能。请不要禁用该服务。
AirPortBaseStationAgent
这个是Apple的无线基站设备搜索服务。如果你没有apple的无线基站设备,可以禁用它。
Spotlight
如果你不喜欢用spotlight,可以禁用它。详见文末给出的“Mac OS启动服务优化高级篇(launchd tuning)”里的优化方法。
/usr/sbin/blued
蓝牙支持服务。如果你不想使用蓝牙,可以禁用它。
cupsd
打印机支持。如果你不想用打印机,可以禁用该服务。
SharedServices.Agent
Apple的MobileMe服务,如果你不使用,可以禁用该服务。
参考资料
daemons和services(守护进程和服务)
https://blog.csdn.net/datacloud/article/details/7327364mac守护进程启动与停止
https://blog.csdn.net/n1ghtsir/article/details/48526375Mac OS守护进程(服务)列表及优化建议
https://www.cnblogs.com/skyming/articles/3979208.htmlMac OS启动服务优化高级篇(launchd tuning)禁用某些服务
https://blog.csdn.net/testcs_dn/article/details/67634582