如何等待非子进程退出

发布于 2024-07-28 00:10:18 字数 129 浏览 4 评论 0 原文

对于子进程,wait()waitpid() 函数可用于暂停当前进程的执行,直到子进程退出。 但该函数不能用于非子进程。

是否还有另一个函数可以等待任何进程的退出?

For child processes, the wait() and waitpid() functions can be used to suspends execution of the current process until a child has exited. But this function can not be used for non-child processes.

Is there another function, which can wait for exit of any process ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(14

夕嗳→ 2024-08-04 00:10:18

没有与 wait() 等效的东西。 通常的做法是使用kill(pid, 0)进行轮询,并查找ESRCH的返回值-1和errno来指示进程离开了。

更新:从 linux 内核 5.3 开始,有一个 pidfd_open syscall,它为给定的 pid 创建一个 fd,当 pid 退出时可以轮询该 fd 以获取通知。

Nothing equivalent to wait(). The usual practice is to poll using kill(pid, 0) and looking for return value -1 and errno of ESRCH to indicate that the process is gone.

Update: Since linux kernel 5.3 there is a pidfd_open syscall, which creates an fd for a given pid, which can be polled to get notification when pid has exited.

记忆消瘦 2024-08-04 00:10:18

在 BSD 和 OS X 上,您可以使用 kqueue 和 EVFILT_PROC+NOTE_EXIT 来完成此操作。 无需投票。 不幸的是,Linux 上没有类似的东西。

On BSDs and OS X, you can use kqueue with EVFILT_PROC+NOTE_EXIT to do exactly that. No polling required. Unfortunately there's no Linux equivalent.

澉约 2024-08-04 00:10:18

到目前为止,我已经找到了三种在 Linux 上执行此操作的方法:

  • 轮询:经常检查进程是否存在,可以使用 kill 或测试 / 是否存在proc/$pid,如大多数其他答案一样
  • 使用 ptrace 系统调用像调试器一样附加到进程,以便在它退出时收到通知,如 a3nm 的答案
  • 使用 netlink 接口监听 PROC_EVENT_EXIT 消息 - 这样内核每次进程退出时都会告诉您的程序,您只需等待正确的进程 ID。 我只在互联网上的一个地方看到过这种描述。

无耻的插件:我正在开发一个程序(当然是开源的;GPLv2),它可以执行以下任何操作三。

So far I've found three ways to do this on Linux:

  • Polling: you check for the existence of the process every so often, either by using kill or by testing for the existence of /proc/$pid, as in most of the other answers
  • Use the ptrace system call to attach to the process like a debugger so you get notified when it exits, as in a3nm's answer
  • Use the netlink interface to listen for PROC_EVENT_EXIT messages - this way the kernel tells your program every time a process exits and you just wait for the right process ID. I've only seen this described in one place on the internet.

Shameless plug: I'm working on a program (open source of course; GPLv2) that does any of the three.

秋千易 2024-08-04 00:10:18

您还可以创建一个套接字或 FIFO 并读取它们。 FIFO 特别简单:将您孩子的标准输出与 FIFO 连接并读取。 读取将阻塞,直到子进程退出(出于任何原因)或直到它发出一些数据。 因此,您需要一个小循环来丢弃不需要的文本数据。

如果您有权访问子进程的源代码,请在子进程启动时打开 FIFO 进行写入,然后就可以忽略它。 当子进程终止时,操作系统将清除打开的文件描述符,并且您等待的“父”进程将被唤醒。

现在,这可能是一个不是您启动或拥有的过程。 在这种情况下,您可以将二进制可执行文件替换为启动真正二进制文件的脚本,同时还添加如上所述的监视。

You could also create a socket or a FIFO and read on them. The FIFO is especially simple: Connect the standard output of your child with the FIFO and read. The read will block until the child exits (for any reason) or until it emits some data. So you'll need a little loop to discard the unwanted text data.

If you have access to the source of the child, open the FIFO for writing when it starts and then simply forget about it. The OS will clean the open file descriptor when the child terminates and your waiting "parent" process will wake up.

Now this might be a process which you didn't start or own. In that case, you can replace the binary executable with a script that starts the real binary but also adds monitoring as explained above.

暗藏城府 2024-08-04 00:10:18

这是一种等待 linux 中的任何进程(不一定是子进程)退出(或被杀死)而无需轮询的方法:

使用 inotify 等待 /proc'pid' 被删除将是完美的解决方案,但不幸的是 inotify不适用于 /proc 等伪文件系统。
但是我们可以将它与进程的可执行文件一起使用。
当该进程仍然存在时,该文件将保持打开状态。
因此我们可以使用 inotify 和 IN_CLOSE_NOWRITE 来阻塞,直到文件关闭。
当然,它可能会因其他原因而关闭(例如,如果具有相同可执行文件的另一个进程退出),因此我们必须通过其他方式过滤这些事件。

我们可以使用kill(pid, 0),但这不能保证它是否仍然是同一个进程。 如果我们真的对此感到偏执,我们可以做点别的事情。

这是一种 100% 安全地防止 pid 重用问题的方法:我们打开伪目录 /proc/'pid',并保持它打开状态直到我们完成。 如果同时使用相同的 pid 创建一个新进程,我们持有的目录文件描述符仍将引用原始进程(或者如果旧进程不再存在,则变得无效),但永远不会引用新进程重用的 pid。 然后我们可以通过openat()检查目录中是否存在“cmdline”文件来检查原始进程是否仍然存在。 当进程退出或被终止时,这些伪文件也不再存在,因此 openat() 将失败。

这是一个示例代码:

// return -1 on error, or 0 if everything went well
int wait_for_pid(int pid)
{
    char path[32];
    int in_fd = inotify_init();
    sprintf(path, "/proc/%i/exe", pid);
    if (inotify_add_watch(in_fd, path, IN_CLOSE_NOWRITE) < 0) {
        close(in_fd);
        return -1;
    }
    sprintf(path, "/proc/%i", pid);
    int dir_fd = open(path, 0);
    if (dir_fd < 0) {
        close(in_fd);
        return -1;
    }

    int res = 0;
    while (1) {
        struct inotify_event event;
        if (read(in_fd, &event, sizeof(event)) < 0) {
            res = -1;
            break;
        }
        int f = openat(dir_fd, "fd", 0);
        if (f < 0) break;
        close(f);
    }

    close(dir_fd);
    close(in_fd);
    return res;
}

Here is a way to wait for any process (not necessarily a child) in linux to exit (or get killed) without polling:

Using inotify to wait for the /proc'pid' to be deleted would be the perfect solution, but unfortunately inotify does not work with pseudo file systems like /proc.
However we can use it with the executable file of the process.
While the process still exists, this file is being held open.
So we can use inotify with IN_CLOSE_NOWRITE to block until the file is closed.
Of course it can be closed for other reasons (e.g. if another process with the same executable exits) so we have to filter those events by other means.

We can use kill(pid, 0), but that can't guarantee if it is still the same process. If we are really paranoid about this, we can do something else.

Here is a way that should be 100% safe against pid-reuse trouble: we open the pseudo directory /proc/'pid', and keep it open until we are done. If a new process is created in the meantime with the same pid, the directory file descriptor that we hold will still refer to the original one (or become invalid, if the old process cease to exist), but will NEVER refer the new process with the reused pid. Then we can check if the original process still exists by checking, for example, if the file "cmdline" exists in the directory with openat(). When a process exits or is killed, those pseudo files cease to exist too, so openat() will fail.

here is an example code:

// return -1 on error, or 0 if everything went well
int wait_for_pid(int pid)
{
    char path[32];
    int in_fd = inotify_init();
    sprintf(path, "/proc/%i/exe", pid);
    if (inotify_add_watch(in_fd, path, IN_CLOSE_NOWRITE) < 0) {
        close(in_fd);
        return -1;
    }
    sprintf(path, "/proc/%i", pid);
    int dir_fd = open(path, 0);
    if (dir_fd < 0) {
        close(in_fd);
        return -1;
    }

    int res = 0;
    while (1) {
        struct inotify_event event;
        if (read(in_fd, &event, sizeof(event)) < 0) {
            res = -1;
            break;
        }
        int f = openat(dir_fd, "fd", 0);
        if (f < 0) break;
        close(f);
    }

    close(dir_fd);
    close(in_fd);
    return res;
}
少年亿悲伤 2024-08-04 00:10:18

您可以使用 ptrace(2) 附加到进程。 在 shell 中,strace -p PID >/dev/null 2>&1 似乎可以工作。 这可以避免忙等待,尽管它会减慢跟踪进程的速度,并且不适用于所有进程(仅适用于您的进程,这比仅子进程好一点)。

You could attach to the process with ptrace(2). From the shell, strace -p PID >/dev/null 2>&1 seems to work. This avoid the busy-waiting, though it will slow down the traced process, and will not work on all processes (only yours, which is a bit better than only child processes).

旧故 2024-08-04 00:10:18

从 linux 内核 5.3 开始,有一个 pidfd_open 系统调用,它创建给定 pid 的 fd,可以轮询它以在 pid 退出时获取通知。

Since linux kernel 5.3 there is a pidfd_open syscall, which creates an fd for a given pid, which can be polled to get notification when pid has exited.

爱*していゐ 2024-08-04 00:10:18

我不知道。 除了混乱的解决方案之外,如果您可以更改要等待的程序,则可以使用信号量。

库函数为sem_open(3)sem_init(3)、 sem_wait(3)、 ...

sem_wait(3) 执行等待,因此您不必像混沌解决方案中那样忙于等待。 当然,使用信号量会让你的程序变得更加复杂,而且可能不值得这么麻烦。

None I am aware of. Apart from the solution from chaos, you can use semaphores if you can change the program you want to wait for.

The library functions are sem_open(3), sem_init(3), sem_wait(3), ...

sem_wait(3) performs a wait, so you don´t have to do busy waiting as in chaos´ solution. Of course, using semaphores makes your programs more complex and it may not be worth the trouble.

2024-08-04 00:10:18

也许可以等待 /proc/[pid] 或 /proc/[pid]/[something] 消失?

有 poll() 和其他文件事件等待函数,也许这可以帮助?

Maybe it could be possible to wait for /proc/[pid] or /proc/[pid]/[something] to disappear?

There are poll() and other file event waiting functions, maybe that could help?

赢得她心 2024-08-04 00:10:18

PR_SET_PDEATHSIG 可用于等待父进程终止

PR_SET_PDEATHSIG can be used to wait for parent process termination

鼻尖触碰 2024-08-04 00:10:18

我的解决方案(使用 inotifywait

这是基于 /proc 文件系统。

我的需要是在容器备份完成后启动第二次(整体)备份。 容器备份由

监视 cron 任务

read -r wpid < <(ps -C backup.sh ho pid)
ls -l /proc/$wpid/fd
total 0
lr-x------ 1 user user 64  1 aoû 09:13 0 -> pipe:[455151052]
lrwx------ 1 user user 64  1 aoû 09:13 1 -> /tmp/#41418 (deleted)
lrwx------ 1 user user 64  1 aoû 09:13 2 -> /tmp/#41418 (deleted)

其中 已删除 条目是由 cron 创建的。 但即使删除,您也可以直接监视文件描述符

inotifywait  /proc/$wpid/fd/1
/proc/511945/fd/1 CLOSE_WRITE,CLOSE 

inotifywait  /proc/$wpid/fd/0
/proc/511945/fd/0 CLOSE_NOWRITE,CLOSE 

注意:我的整体备份运行为root用户! 如果没有,这可能需要 sudo 因为命令是在 cron 会话下运行的!

同一会话

只需测试:在第一个窗口中,点击:

sleep 0.42m <<<'' >/dev/null 2>&1

然后在另一个窗口中:

read -r wpid < <(ps -C sleep wwho pid,cmd| sed 's/ sleep 0\.42m$//p;d')
ls -l /proc/$wpid/fd
total 0
lr-x------ 1 user user 64  1 aoû 09:38 0 -> pipe:[455288137]
l-wx------ 1 user user 64  1 aoû 09:38 1 -> /dev/null
l-wx------ 1 user user 64  1 aoû 09:38 2 -> /dev/null

不要尝试观看 12! 因为它们指向 /dev/null,所以任何访问它们的进程都会触发 inotifywait

inotifywait  /proc/$wpid/fd/0
/proc/531119/fd/0 CLOSE_NOWRITE,CLOSE

经过的时间(以秒为单位)

第一个窗口:

sleep 0.42m <<<'' >/dev/null 2>&1

第二个窗口:

read -r wpid < <(ps -C sleep wwho pid,cmd| sed 's/ sleep 0\.42m$//p;d')
startedAt=$(ps ho lstart $wpid | date -f - +%s)
inotifywait  /proc/$wpid/fd/0;echo $((EPOCHSECONDS-startedAt))
/proc/533967/fd/0 CLOSE_NOWRITE,CLOSE 
25

结论。

使用inotifywait似乎是一个很好的解决方案,主要是监视命令的标准输入(fd/0)。 但这必须根据具体情况进行测试。

My solution (using inotifywait)

This is based on 's /proc filesystem.

My need was to start a 2nd (overall) backup, once containers backups is done. Containers backups is started by .

Watching for cron tasks

read -r wpid < <(ps -C backup.sh ho pid)
ls -l /proc/$wpid/fd
total 0
lr-x------ 1 user user 64  1 aoû 09:13 0 -> pipe:[455151052]
lrwx------ 1 user user 64  1 aoû 09:13 1 -> /tmp/#41418 (deleted)
lrwx------ 1 user user 64  1 aoû 09:13 2 -> /tmp/#41418 (deleted)

Where deleted entries was created by cron. But even if deleted, you could watch for file descriptor directly:

inotifywait  /proc/$wpid/fd/1
/proc/511945/fd/1 CLOSE_WRITE,CLOSE 

or

inotifywait  /proc/$wpid/fd/0
/proc/511945/fd/0 CLOSE_NOWRITE,CLOSE 

Note: My overall backup is run as root user! If no this could require sudo because command is run under cron session!

Same session

Just test: In a 1st window, hit:

sleep 0.42m <<<'' >/dev/null 2>&1

Then in another window:

read -r wpid < <(ps -C sleep wwho pid,cmd| sed 's/ sleep 0\.42m$//p;d')
ls -l /proc/$wpid/fd
total 0
lr-x------ 1 user user 64  1 aoû 09:38 0 -> pipe:[455288137]
l-wx------ 1 user user 64  1 aoû 09:38 1 -> /dev/null
l-wx------ 1 user user 64  1 aoû 09:38 2 -> /dev/null

Don't try to watch for 1 or 2! Because they point to /dev/null, any process acessing to them will trig inotifywait.

inotifywait  /proc/$wpid/fd/0
/proc/531119/fd/0 CLOSE_NOWRITE,CLOSE

Elapsed time in seconds

1st window:

sleep 0.42m <<<'' >/dev/null 2>&1

2nd window:

read -r wpid < <(ps -C sleep wwho pid,cmd| sed 's/ sleep 0\.42m$//p;d')
startedAt=$(ps ho lstart $wpid | date -f - +%s)
inotifywait  /proc/$wpid/fd/0;echo $((EPOCHSECONDS-startedAt))
/proc/533967/fd/0 CLOSE_NOWRITE,CLOSE 
25

Conclusion.

Using inotifywait seem to be a good solution, mostly watching for command's standard input (fd/0). But this must be tested case by case.

没有你我更好 2024-08-04 00:10:18

只需轮询 /proc/[PID]/stat 的值 22 和 2。
值 2 包含可执行文件的名称,22 包含开始时间。
如果它们发生变化,则表明其他进程已采用相同(已释放)的 PI​​D。 因此该方法是非常可靠的。

Simply poll values number 22 and 2 of the /proc/[PID]/stat.
The value 2 contains name of the executable and 22 contains start time.
If they change, some other process has taken the same (freed) PID. Thus the method is very reliable.

单挑你×的.吻 2024-08-04 00:10:18

您可以使用eBPF来实现这一点。

bcc工具包基于eBPF实现了许多优秀的监控能力。 其中exitsnoop跟踪进程终止,显示命令名称和终止原因,
要么是退出信号,要么是致命信号。

   It catches processes of all users, processes in containers,  as  well  as  processes  that
   become zombie.

   This  works by tracing the kernel sched_process_exit() function using dynamic tracing, and
   will need updating to match any changes to this function.

   Since this uses BPF, only the root user can use this tool.

您可以参考该工具进行相关实现。

您可以通过以下链接获取有关该工具的更多信息:

你可以先安装这个工具并使用它看看是否满足你的需求,然后参考它的实现进行编码,或者使用它提供的一些库来实现自己的功能。

exitsnoop 示例:

   Trace all process termination
          # exitsnoop

   Trace all process termination, and include timestamps:
          # exitsnoop -t

   Exclude successful exits, only include non-zero exit codes and fatal signals:
          # exitsnoop -x

   Trace PID 181 only:
          # exitsnoop -p 181

   Label each output line with 'EXIT':
          # exitsnoop --label EXIT

另一个选项

使用 Linux 的 PROC_EVENTS 等待(非子)进程退出

参考项目:
https://github.com/stormc/waitforpid

项目中提到的

使用 Linux 的 PROC_EVENTS 等待(非子)进程退出。 谢谢
授予 waitforpid 允许的 CAP_NET_ADMIN POSIX 功能
二进制文件,不需要设置suid root。 你需要一个 Linux 内核
已启用 CONFIG_PROC_EVENTS。

You can use eBPF to achieve this.

The bcc toolkit implements many excellent monitoring capabilities based on eBPF. Among them, exitsnoop traces process termination, showing the command name and reason for termination,
either an exit or a fatal signal.

   It catches processes of all users, processes in containers,  as  well  as  processes  that
   become zombie.

   This  works by tracing the kernel sched_process_exit() function using dynamic tracing, and
   will need updating to match any changes to this function.

   Since this uses BPF, only the root user can use this tool.

You can refer to this tool for related implementation.

You can get more information about this tool from the link below:

You can first install this tool and use it to see if it meets your needs, and then refer to its implementation for coding, or use some of the libraries it provides to implement your own functions.

exitsnoop examples:

   Trace all process termination
          # exitsnoop

   Trace all process termination, and include timestamps:
          # exitsnoop -t

   Exclude successful exits, only include non-zero exit codes and fatal signals:
          # exitsnoop -x

   Trace PID 181 only:
          # exitsnoop -p 181

   Label each output line with 'EXIT':
          # exitsnoop --label EXIT

Another option

Wait for a (non-child) process' exit using Linux's PROC_EVENTS

Reference project:
https://github.com/stormc/waitforpid

mentioned in the project:

Wait for a (non-child) process' exit using Linux's PROC_EVENTS. Thanks
to the CAP_NET_ADMIN POSIX capability permitted to the waitforpid
binary, it does not need to be set suid root. You need a Linux kernel
having CONFIG_PROC_EVENTS enabled.

绳情 2024-08-04 00:10:18

使用 kqueue 适用于 macOS 的 @Hongli 答案。 我用快速实现它

/// Wait any pids, including non-child pid. Block until all pids exit.
/// - Parameters:
///   - timeout: wait until interval, nil means no timeout
/// - Throws: WaitOtherPidError
/// - Returns: isTimeout
func waitOtherPids(_ pids: [Int32], timeout: TimeInterval? = nil) throws -> Bool {
    
    // create a kqueue
    let kq = kqueue()
    if kq == -1 {
        throw WaitOtherPidError.createKqueueFailed(String(cString: strerror(errno)!))
    }
    
    // input
    // multiple changes is OR relation, kevent will return if any is match
    var changes: [Darwin.kevent] = pids.map({ pid in
        Darwin.kevent.init(ident: UInt(pid), filter: Int16(EVFILT_PROC), flags: UInt16(EV_ADD | EV_ENABLE), fflags: NOTE_EXIT, data: 0, udata: nil)
    })
    
    let timeoutDeadline = timeout.map({ Date(timeIntervalSinceNow: $0)})
    let remainTimeout: () ->timespec? = {
        if let deadline = timeoutDeadline {
            let d = max(deadline.timeIntervalSinceNow, 0)
            let fractionalPart = d - TimeInterval(Int(d))
            return timespec(tv_sec: Int(d), tv_nsec: Int(fractionalPart * 1000 * 1000 * 1000))
        } else {
            return nil
        }
    }
    
    // output
    var events = changes.map{ _ in Darwin.kevent.init() }
    
    while !changes.isEmpty {
        
        // watch changes
        // sync method
        let numOfEvent: Int32
        if var timeout = remainTimeout() {
            numOfEvent = kevent(kq, changes, Int32(changes.count), &events, Int32(events.count), &timeout);
        } else {
            numOfEvent = kevent(kq, changes, Int32(changes.count), &events, Int32(events.count), nil);
        }
        
        if numOfEvent < 0 {
            throw WaitOtherPidError.keventFailed(String(cString: strerror(errno)!))
        }
        if numOfEvent == 0 {
            // timeout. Return directly.
            return true
        }
        
        // handle the result
        let realEvents = events[0..<Int(numOfEvent)]
        let handledPids = Set(realEvents.map({ $0.ident }))
        changes = changes.filter({ c in
            !handledPids.contains(c.ident)
        })

        for event in realEvents {
            if Int32(event.flags) & EV_ERROR > 0 { // @see 'man kevent'
                let errorCode = event.data
                if errorCode == ESRCH {
                    // "The specified process to attach to does not exist"
                    // ingored
                } else {
                    print("[Error] kevent result failed with code \(errorCode), pid \(event.ident)")
                }
            } else {
                // succeeded event, pid exit
            }
        }
    }
    return false
}
enum WaitOtherPidError: Error {
    case createKqueueFailed(String)
    case keventFailed(String)
}

Appricate @Hongli's answer for macOS with kqueue. I implement it with swift

/// Wait any pids, including non-child pid. Block until all pids exit.
/// - Parameters:
///   - timeout: wait until interval, nil means no timeout
/// - Throws: WaitOtherPidError
/// - Returns: isTimeout
func waitOtherPids(_ pids: [Int32], timeout: TimeInterval? = nil) throws -> Bool {
    
    // create a kqueue
    let kq = kqueue()
    if kq == -1 {
        throw WaitOtherPidError.createKqueueFailed(String(cString: strerror(errno)!))
    }
    
    // input
    // multiple changes is OR relation, kevent will return if any is match
    var changes: [Darwin.kevent] = pids.map({ pid in
        Darwin.kevent.init(ident: UInt(pid), filter: Int16(EVFILT_PROC), flags: UInt16(EV_ADD | EV_ENABLE), fflags: NOTE_EXIT, data: 0, udata: nil)
    })
    
    let timeoutDeadline = timeout.map({ Date(timeIntervalSinceNow: $0)})
    let remainTimeout: () ->timespec? = {
        if let deadline = timeoutDeadline {
            let d = max(deadline.timeIntervalSinceNow, 0)
            let fractionalPart = d - TimeInterval(Int(d))
            return timespec(tv_sec: Int(d), tv_nsec: Int(fractionalPart * 1000 * 1000 * 1000))
        } else {
            return nil
        }
    }
    
    // output
    var events = changes.map{ _ in Darwin.kevent.init() }
    
    while !changes.isEmpty {
        
        // watch changes
        // sync method
        let numOfEvent: Int32
        if var timeout = remainTimeout() {
            numOfEvent = kevent(kq, changes, Int32(changes.count), &events, Int32(events.count), &timeout);
        } else {
            numOfEvent = kevent(kq, changes, Int32(changes.count), &events, Int32(events.count), nil);
        }
        
        if numOfEvent < 0 {
            throw WaitOtherPidError.keventFailed(String(cString: strerror(errno)!))
        }
        if numOfEvent == 0 {
            // timeout. Return directly.
            return true
        }
        
        // handle the result
        let realEvents = events[0..<Int(numOfEvent)]
        let handledPids = Set(realEvents.map({ $0.ident }))
        changes = changes.filter({ c in
            !handledPids.contains(c.ident)
        })

        for event in realEvents {
            if Int32(event.flags) & EV_ERROR > 0 { // @see 'man kevent'
                let errorCode = event.data
                if errorCode == ESRCH {
                    // "The specified process to attach to does not exist"
                    // ingored
                } else {
                    print("[Error] kevent result failed with code \(errorCode), pid \(event.ident)")
                }
            } else {
                // succeeded event, pid exit
            }
        }
    }
    return false
}
enum WaitOtherPidError: Error {
    case createKqueueFailed(String)
    case keventFailed(String)
}

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文