A daemon is a special process that runs in the background and is used to perform specific system tasks. This article will take you through how to implement daemon in PHP and introduce what you need to pay attention to in programming.
PHP implementation daemon can be implemented through the pcntl
and posix
extensions.
Things that need to be noted in programming are:
- Let the main process leave the terminal through the second
pcntl_fork()
andposix_setsid
#Pass - pcntl_signal()
Ignore or handle
SIGHUPsignal
Multi-process programs need to pass twice - pcntl_fork()
or
pcntl_signal()Ignore the
SIGCHLDsignal to prevent the child process from becoming a Zombie process
Set the file permission mask through - umask()
to prevent inheritance of file permissions The resulting permission impact function
redirects the - STDIN/STDOUT/STDERR
of the running process to
/dev/nullor other streams
- If you start through root, change to a low-privilege user identity when running
- Timely
- chdir()
Prevent operation error paths
Consider restarting multi-process programs regularly to prevent memory leaks
What is a daemon
The protagonist of the articleDaemon, the definition on Wikipedia is:
In a multi-tasking computer operating system, the daemon process (English: daemon, /?di?m?n/ or /?de?m?n/) is a computer program that executes in the background. Such programs will be initialized as processes. The names of daemon programs usually end with the letter "d": for example, syslogd refers to the daemon that manages system logs.Usually, the daemon process does not have any existing parent process (that is, PPID=1), and is directly under init in the UNIX system process hierarchy. A daemon program usually makes itself a daemon by running fork on a child process, and then terminating its parent process immediately so that the child process can run under init. This method is often called "shelling."
Advanced Programming in UNIX Environment (Second Edition) (hereinafter referred to as APUE) There is a cloud in Chapter 13:
Daemon processes have also become The daemon process is a process with a long life cycle. They are often started when the system boots and terminate only when the system is shut down. Because they don't have a controlling terminal, they are said to run in the background.It is noted here that daemon has the following characteristics:
- No terminal
- Running in the background
- The parent process pid is 1
ps -ax or
ps -ef, where
-x means it will be listed There is no process controlling the terminal.
Implementation concerns
Second fork and setsid
fork system call
Thepcntl extension in PHP implements the
pcntl_fork() function, which is used to fork a new process in PHP.
setsid system call
setsid The system call is used to create a new session and set the process group id.
There are several concepts here:Session,
Process Group.
&) will also be killed after the terminal is closed, that is, the
SIGHUP issued when the control terminal is disconnected is not handled properly. signal, and the default behavior of
SIGHUP signal for a process is to exit the process.
Callsetsid
After the system call, the current process will create a new process group. If the terminal is not opened in the current process, then there will be no control terminal in this process group, and there will be no control terminal. There will be a problem of killing the process by closing the terminal.
The posix
extension in PHP implements the posix_setsid()
function, which is used to set a new process group in PHP.
Orphan process
The parent process exits before the child process, and the child process will become an orphan process.
The init process will adopt the orphan process, that is, the ppid of the orphan process becomes 1.
The role of secondary fork
First of all, the setsid
system call cannot be called by the process group leader and will return -1.
The sample code for the second fork operation is as follows:
$pid1 = pcntl_fork(); if ($pid1 > 0) { exit(0); } else if ($pid1 < 0) { exit("Failed to fork 1\n"); } if (-1 == posix_setsid()) { exit("Failed to setsid\n"); } $pid2 = pcntl_fork(); if ($pid2 > 0) { exit(0); } else if ($pid2 < 0) { exit("Failed to fork 2\n"); }
Assume that we execute the application in the terminal and the process is a. The first fork will generate a child process b. If the fork is successful, Parent process a exits. b As an orphan process, it is hosted by the init process.
At this time, process b is in process group a, and process b calls posix_setsid
to request the generation of a new process group. After the call is successful, the current process group becomes b.
At this time, process b has actually separated from any control terminal. Routine:
<?php cli_set_process_title('process_a'); $pidA = pcntl_fork(); if ($pidA > 0) { exit(0); } else if ($pidA < 0) { exit(1); } cli_set_process_title('process_b'); if (-1 === posix_setsid()) { exit(2); } while(true) { sleep(1); }
After executing the program:
? ~ php56 2fork1.php ? ~ ps ax | grep -v grep | grep -E 'process_|PID' PID TTY STAT TIME COMMAND 28203 ? Ss 0:00 process_b
From the results of ps, the TTY of process_b Has it become ?
, that is, there is no corresponding control terminal.
When the code reaches this point, it seems that the function has been completed. process_b has not been killed after closing the terminal, but why is there a second fork operation?
An answer on StackOverflow is well written:
The second fork(2) is there to ensure that the new process is not a session leader , so it won't be able to (accidentally) allocate a controlling terminal, since daemons are not supposed to ever have a controlling terminal.
This is to prevent the actual working process from actively associating or The control terminal is accidentally associated. The new process generated after forking again cannot apply for association with the control terminal because it is not the process group leader.
To sum up, the function of secondary fork and setsid is to generate a new process group and prevent the working process from being associated with the control terminal.
SIGHUP signal handling
The default action of a process receiving the SIGHUP
signal is to terminate the process.
And SIGHUP
will be issued under the following circumstances:
- The control terminal is disconnected, and SIGHUP is sent to the process group leader
- Process group When the group leader exits, SIGHUP will be sent to the foreground process in the process group
- SIGHUP is often used to notify the process to reload the configuration file (mentioned in APUE, the daemon is considered unlikely to receive it because it does not have a control terminal) This is a signal, so we choose to reuse it)
Since the actual working process is not in the foreground process group, and the leader of the process group has exited and does not control the terminal, of course there will be no processing under normal circumstances. Problem, however, in order to prevent the process from exiting due to the accidental receipt of SIGHUP
, and in order to follow the conventions of daemon programming, this signal should still be processed.
Zombie process processing
What is the Zombie process
Simply put, the child process first When the parent process exits, the parent process does not call the wait
system call processing, and the process becomes a Zombie process.
When the child process exits before the parent process, it will send the SIGCHLD
signal to the parent process. If the parent process does not handle it, the child process will also become a Zombie process.
Zombie processes will occupy the number of processes that can be forked. Too many zombie processes will result in the inability to fork new processes.
In addition, in the Linux system, a process whose ppid is the init process will be recycled and managed by the init process after it becomes a Zombie.
Zombie process processing
From the characteristics of the Zombie process, for multi-process daemons, this problem can be solved in two ways:
- Parent process processing
SIGCHLD
Signal - Let the child process be taken over by init
Parent process processing signal Needless to say, register the signal processing callback function and call Recycling method is enough.
For the child process to be taken over by init, you can use the method of fork twice, so that the child process a from the first fork can then fork out the actual working process b, and let a exit first, so that b becomes Orphan process so that it can be hosted by the init process.
umask
umask will be inherited from the parent process, affecting the permissions to create files.
PHP Manual mentioned:
umask() sets PHP's umask to mask & 0777 and returns the original umask. When PHP is used as a server module, the umask is restored after each request.
If the umask of the parent process is not set properly, unexpected effects will occur when performing some file operations:
? ~ cat test_umask.php <?php chdir('/tmp'); umask(0066); mkdir('test_umask', 0777); ? ~ php test_umask.php ? ~ ll /tmp | grep umask drwx--x--x 2 root root 4.0K 8月 22 17:35 test_umask
所以,為了保證每一次都能按照預(yù)期的權(quán)限操作文件,需要置0 umask 值。
重定向0/1/2
這里的0/1/2分別指的是 STDIN/STDOUT/STDERR
,即標(biāo)準(zhǔn)輸入/輸出/錯(cuò)誤三個(gè)流。
樣例
首先來(lái)看一個(gè)樣例:
上述代碼幾乎完成了文章最開始部分提及的各個(gè)方面,唯一不同的是沒(méi)有對(duì)標(biāo)準(zhǔn)流做處理。通過(guò)
php not_redirect_std_stream_daemon.php
指令也能讓程序在后臺(tái)進(jìn)行。在
sleep
的間隙,關(guān)閉終端,會(huì)發(fā)現(xiàn)進(jìn)程退出。通過(guò)
strace
觀察系統(tǒng)調(diào)用的情況:? ~ strace -p 6723 Process 6723 attached - interrupt to quit restart_syscall(<... resuming interrupted call ...>) = 0 write(1, "1503417004\n", 11) = 11 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 nanosleep({10, 0}, 0x7fff71a30ec0) = 0 write(1, "1503417014\n", 11) = -1 EIO (Input/output error) close(2) = 0 close(1) = 0 munmap(0x7f35abf59000, 4096) = 0 close(0) = 0發(fā)現(xiàn)發(fā)生了 EIO 錯(cuò)誤,導(dǎo)致進(jìn)程退出。
原因很簡(jiǎn)單,即我們編寫的 daemon 程序使用了當(dāng)時(shí)啟動(dòng)時(shí)終端提供的標(biāo)準(zhǔn)流,當(dāng)終端關(guān)閉時(shí),標(biāo)準(zhǔn)流變得不可讀不可寫,一旦嘗試讀寫,會(huì)導(dǎo)致進(jìn)程退出。
在信海龍的博文《一個(gè)echo引起的進(jìn)程崩潰》中也提到過(guò)類似的問(wèn)題。
解決方案
APUE 樣例
APUE 13.3中提到過(guò)一條編程規(guī)則(第6條):
某些守護(hù)進(jìn)程打開
/dev/null
時(shí)期具有文件描述符0、1和2,這樣,任何一個(gè)視圖讀標(biāo)準(zhǔn)輸入、寫標(biāo)準(zhǔn)輸出或者標(biāo)準(zhǔn)錯(cuò)誤的庫(kù)例程都不會(huì)產(chǎn)生任何效果。因?yàn)槭刈o(hù)進(jìn)程并不與終端設(shè)備相關(guān)聯(lián),所以不能在終端設(shè)備上顯示器輸出,也無(wú)從從交互式用戶那里接受輸入。及時(shí)守護(hù)進(jìn)程是從交互式會(huì)話啟動(dòng)的,但因?yàn)槭刈o(hù)進(jìn)程是在后臺(tái)運(yùn)行的,所以登錄會(huì)話的終止并不影響守護(hù)進(jìn)程。如果其他用戶在同一終端設(shè)備上登錄,我們也不會(huì)在該終端上見到守護(hù)進(jìn)程的輸出,用戶也不可期望他們?cè)诮K端上的輸入會(huì)由守護(hù)進(jìn)程讀取。簡(jiǎn)單來(lái)說(shuō):
- daemon 不應(yīng)使用標(biāo)準(zhǔn)流
- 0/1/2 要設(shè)定成 /dev/null
例程中使用:
for (i = 0; i < rl.rlim_max; i++) close(i); fd0 = open("/dev/null", O_RDWR); fd1 = dup(0); fd2 = dup(0);
實(shí)現(xiàn)了這一個(gè)功能。dup()
(參考手冊(cè))系統(tǒng)調(diào)用會(huì)復(fù)制輸入?yún)?shù)中的文件描述符,并復(fù)制到最小的未分配文件描述符上。所以上述例程可以理解為:
關(guān)閉所有可以打開的文件描述符,包括標(biāo)準(zhǔn)輸入輸出錯(cuò)誤; 打開/dev/null并賦值給變量fd0,因?yàn)闃?biāo)準(zhǔn)輸入已經(jīng)關(guān)閉了,所以/dev/null會(huì)綁定到0,即標(biāo)準(zhǔn)輸入; 因?yàn)樽钚∥捶峙湮募枋龇麨?,復(fù)制文件描述符0到文件描述符1,即標(biāo)準(zhǔn)輸出也綁定到/dev/null; 因?yàn)樽钚∥捶峙湮募枋龇麨?,復(fù)制文件描述符0到文件描述符2,即標(biāo)準(zhǔn)錯(cuò)誤也綁定到/dev/null;復(fù)制代碼
開源項(xiàng)目實(shí)現(xiàn):Workerman
Workerman 中的 Worker.php 中的 resetStd()
方法實(shí)現(xiàn)了類似的操作。
/** * Redirect standard input and output. * * @throws Exception */ public static function resetStd() { if (!self::$daemonize) { return; } global $STDOUT, $STDERR; $handle = fopen(self::$stdoutFile, "a"); if ($handle) { unset($handle); @fclose(STDOUT); @fclose(STDERR); $STDOUT = fopen(self::$stdoutFile, "a"); $STDERR = fopen(self::$stdoutFile, "a"); } else { throw new Exception('can not open stdoutFile ' . self::$stdoutFile); } }
Workerman 中如此實(shí)現(xiàn),結(jié)合博文,可能與 PHP 的 GC 機(jī)制有關(guān),對(duì)于 fd 0 1 2來(lái)說(shuō),PHP 會(huì)維持對(duì)這三個(gè)資源的引用計(jì)數(shù),在直接 fclose 之后,會(huì)使得這幾個(gè) fd 對(duì)應(yīng)的資源類型的變量引用計(jì)數(shù)為0,導(dǎo)致觸發(fā)回收。所需要做的就是將這些變量變?yōu)槿肿兞?,保證引用的存在。
推薦學(xué)習(xí):《PHP視頻教程》
The above is the detailed content of What is a daemon? How to implement daemon in PHP?. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undress AI Tool
Undress images for free

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

The method to get the current session ID in PHP is to use the session_id() function, but you must call session_start() to successfully obtain it. 1. Call session_start() to start the session; 2. Use session_id() to read the session ID and output a string similar to abc123def456ghi789; 3. If the return is empty, check whether session_start() is missing, whether the user accesses for the first time, or whether the session is destroyed; 4. The session ID can be used for logging, security verification and cross-request communication, but security needs to be paid attention to. Make sure that the session is correctly enabled and the ID can be obtained successfully.

To extract substrings from PHP strings, you can use the substr() function, which is syntax substr(string$string,int$start,?int$length=null), and if the length is not specified, it will be intercepted to the end; when processing multi-byte characters such as Chinese, you should use the mb_substr() function to avoid garbled code; if you need to intercept the string according to a specific separator, you can use exploit() or combine strpos() and substr() to implement it, such as extracting file name extensions or domain names.

UnittestinginPHPinvolvesverifyingindividualcodeunitslikefunctionsormethodstocatchbugsearlyandensurereliablerefactoring.1)SetupPHPUnitviaComposer,createatestdirectory,andconfigureautoloadandphpunit.xml.2)Writetestcasesfollowingthearrange-act-assertpat

In PHP, the most common method is to split the string into an array using the exploit() function. This function divides the string into multiple parts through the specified delimiter and returns an array. The syntax is exploit(separator, string, limit), where separator is the separator, string is the original string, and limit is an optional parameter to control the maximum number of segments. For example $str="apple,banana,orange";$arr=explode(",",$str); The result is ["apple","bana

JavaScript data types are divided into primitive types and reference types. Primitive types include string, number, boolean, null, undefined, and symbol. The values are immutable and copies are copied when assigning values, so they do not affect each other; reference types such as objects, arrays and functions store memory addresses, and variables pointing to the same object will affect each other. Typeof and instanceof can be used to determine types, but pay attention to the historical issues of typeofnull. Understanding these two types of differences can help write more stable and reliable code.

std::chrono is used in C to process time, including obtaining the current time, measuring execution time, operation time point and duration, and formatting analysis time. 1. Use std::chrono::system_clock::now() to obtain the current time, which can be converted into a readable string, but the system clock may not be monotonous; 2. Use std::chrono::steady_clock to measure the execution time to ensure monotony, and convert it into milliseconds, seconds and other units through duration_cast; 3. Time point (time_point) and duration (duration) can be interoperable, but attention should be paid to unit compatibility and clock epoch (epoch)

In PHP, to pass a session variable to another page, the key is to start the session correctly and use the same $_SESSION key name. 1. Before using session variables for each page, it must be called session_start() and placed in the front of the script; 2. Set session variables such as $_SESSION['username']='JohnDoe' on the first page; 3. After calling session_start() on another page, access the variables through the same key name; 4. Make sure that session_start() is called on each page, avoid outputting content in advance, and check that the session storage path on the server is writable; 5. Use ses

ToaccessenvironmentvariablesinPHP,usegetenv()orthe$_ENVsuperglobal.1.getenv('VAR_NAME')retrievesaspecificvariable.2.$_ENV['VAR_NAME']accessesvariablesifvariables_orderinphp.iniincludes"E".SetvariablesviaCLIwithVAR=valuephpscript.php,inApach
