生成一个新的 shell 进程作为另一个进程组 (Haskell) 的成员



Problem.我正在尝试生成一个新的 shell 进程,并强制它成为另一个现有进程组的成员,这样如果我的 Haskell 程序死了,生成的进程就可以保证存活。以下抱怨我的repl中的权限:

import System.Process
import Data.Int
createProcessWithGroup :: ProcessGroupID -> FilePath -> [String] -> Maybe [(String, String)] -> IO ProcessID
createProcessWithGroup pgid exe args env = forkProcess $ do
joinProcessGroup pgid
executeFile exe True args env
pidStr <- readProcess "pgrep" ["firefox"] []
let pid = CPid ((read pidStr) :: Int32)
gpid <- getProcessGroupIDOf pid -- :: ProcessID -> IO ProcessGroupID
putStrLn $ "gpid: " ++ (show gpid)
pid <- createProcessWithGroup gpid "gvim" [] Nothing
putStrLn $ "pid: " ++ (show pid)

输出

joinProcessGroup: permission denied (Operation not permitted)
pid: 26341

我无法按root运行原始的Haskell进程。有没有办法解决这个问题?

您只能在同一会话中将流程的进程组更改为(现有(进程组。 这是操作系统限制。 请参阅setpgid调用的手册页。 尝试将进程组更改为不同会话中的组将导致 EPERM(即使您是 root(。

最有可能的是,您的Haskell进程与firefox进程不在同一会话中,这可以解释您遇到的错误。 我发现如果我从窗口管理器启动 Firefox 并在终端中运行以下程序(因此 Firefox 属于gnome-shell的会话,而 Haskell 进程属于我的bashshell 的会话(,我会得到:

gpid: 2692
pid: 12093
Setpgid.hs: joinProcessGroup: permission denied (Operation not permitted)

另一方面,如果我完全关闭Firefox,在运行程序之前使用firefox &在后台从同一终端重新启动它,joinProcessGroup工作正常(尽管我收到不同的错误,因为我没有安装gvim(:

gpid: 12125
pid: 12359
Setpgid.hs: gvim: executeFile: does not exist (No such file or directory)

这是我运行的测试程序:

import System.Posix
import System.Process
import Data.Int
createProcessWithGroup :: ProcessGroupID -> FilePath -> [String] -> Maybe [(String, String)] -> IO ProcessID
createProcessWithGroup pgid exe args env = forkProcess $ do
joinProcessGroup pgid
executeFile exe True args env
main :: IO ()
main = do
pidStr <- readProcess "pgrep" ["firefox"] []
let pid = CPid ((read pidStr) :: Int32)
gpid <- getProcessGroupIDOf pid -- :: ProcessID -> IO ProcessGroupID
putStrLn $ "gpid: " ++ (show gpid)
pid <- createProcessWithGroup gpid "gvim" [] Nothing
putStrLn $ "pid: " ++ (show pid)

如果您只需要将当前进程与当前进程组分离,请使用setsid(2)并将标准流文件描述符重定向到/dev/null

import System.Posix.Process
import System.Posix.IO
stdFds = [stdInput, stdOutput, stdError]
createProcessAsSessionLeader :: FilePath -> [String] -> Maybe [(String, String)] -> IO ProcessID
createProcessAsSessionLeader exe args env = forkProcess $ do
-- use getProcessId >>= createProcessGroupFor to do setpgid(getpid(), 0) instead
createSession
-- a bracket or finally here is unnecessary here
-- since the child process is short-lived.
fd <- openFd "/dev/null" ReadWrite Nothing defaultFileFlags
mapM_ (dupTo fd) stdFds
closeFd fd
executeFile exe True args env

但你也可以只做:

import System.Process
import System.IO
createProcessAsSessionLeader :: FilePath -> [String] -> Maybe [(String, String)] -> IO ProcessHandle
createProcessAsSessionLeader exe args env' =
fmap ((_, _, _, p) -> p) $
withBinaryFile "/dev/null" ReadWriteMode $ h ->
let s = UseHandle h
in createProcess $ proc exe args {
env = env', std_in = s, std_out = s, std_err = s,
-- use create_group = True to do setpgid(getpid(), 0) instead
new_session = True
}

如果确实要加入现有进程组,请确保附件和附件具有相同的会话 ID(与登录会话不完全相同(,但通常情况并非如此。否则,守护程序、GUI 程序启动和屏幕多路复用器也使用此setsid(2)用于此特定目的。

如果您不想完全放弃该过程,那么仅屏蔽 stdin 就足够了,这样 stdout 和 stderr就不会被丢弃。在任何情况下,子进程对终端发送的信号都没有响应,因为使进程成为会话领导者会将其与当前控制终端解除关联(除非再次连接(。

相关内容

最新更新