Ruby 2.6.5如何调用POSIX的tcsetpgrp设置终端前台进程组?
Great question! Since Ruby 2.6.5 doesn't include built-in wrappers for tcsetpgrp, we have two reliable approaches to achieve your goal—both aligning with your Python implementation's logic. Let's dive in:
1. Use FFI to Call POSIX Functions Directly (No Compilation Hassle)
The easiest way to avoid writing and troubleshooting custom .so files is using Ruby's FFI (Foreign Function Interface) to directly invoke the tcsetpgrp and tcgetpgrp functions from the system's libc library. This skips manual compilation entirely.
Step-by-Step Implementation
First, install the ffi gem:
gem install ffi
Then use this code to replicate your Python logic:
require 'ffi' # Wrap libc's terminal process group functions module LibC extend FFI::Library ffi_lib FFI::Library::LIBC # Auto-links to system libc attach_function :tcgetpgrp, [:int], :pid_t attach_function :tcsetpgrp, [:int, :pid_t], :int end def become_tty_fg # Create a new process group (equivalent to Python's os.setpgrp()) Process.setpgid(0, 0) # Ignore SIGTTOU temporarily to avoid being stopped when modifying the terminal old_handler = Signal.trap(:SIGTTOU, :IGNORE) # Open the controlling terminal to get a file descriptor tty_file = File.open('/dev/tty', 'r+') tty_fd = tty_file.fileno # Set the current process group as the terminal's foreground group LibC.tcsetpgrp(tty_fd, Process.getpgid(0)) # Restore SIGTTOU handling so Ctrl+Z can suspend the child process Signal.trap(:SIGTTOU, old_handler) tty_file.close end # Example usage: Spawn a child process with the foreground terminal control spawn('sleep 60', pgroup: false, preexec: method(:become_tty_fg)) Process.wait
Key Notes:
Process.setpgid(0, 0)makes the child process the leader of a new process group (matches Python'sos.setpgrp()).- The
pgroup: falseflag inspawntells Ruby not to auto-create a process group, letting us handle it manually. - FFI handles linking to libc automatically, so you won't run into missing
.sofile issues.
2. Fix the C Extension .so Problem (If You Prefer Compiled Code)
If you want to write a custom C extension, your earlier issue with missing .so files likely stems from incorrect compilation. Use Ruby's built-in mkmf tool to generate a proper Makefile that links to libc automatically.
Step-by-Step Implementation
- Create a C source file (
ttygrp.c):
#include "ruby.h" #include <unistd.h> // Wrapper for tcsetpgrp static VALUE tcsetpgrp_wrapper(VALUE self, VALUE fd, VALUE pgrp) { int ret = tcsetpgrp(NUM2INT(fd), NUM2PIDT(pgrp)); return INT2NUM(ret); } // Wrapper for tcgetpgrp static VALUE tcgetpgrp_wrapper(VALUE self, VALUE fd) { pid_t pgrp = tcgetpgrp(NUM2INT(fd)); return PIDT2NUM(pgrp); } // Register functions with Ruby's Process module void Init_ttygrp(void) { VALUE mProcess = rb_const_get(rb_cObject, rb_intern("Process")); rb_define_module_function(mProcess, "tcsetpgrp", tcsetpgrp_wrapper, 2); rb_define_module_function(mProcess, "tcgetpgrp", tcgetpgrp_wrapper, 1); }
- Create an
extconf.rbfile to generate the Makefile:
require 'mkmf' create_makefile('ttygrp')
- Compile the extension:
ruby extconf.rb make
- Use the extension in your Ruby script:
require './ttygrp' def become_tty_fg Process.setpgid(0, 0) old_handler = Signal.trap(:SIGTTOU, :IGNORE) tty_fd = File.open('/dev/tty', 'r+').fileno Process.tcsetpgrp(tty_fd, Process.getpgid(0)) Signal.trap(:SIGTTOU, old_handler) end # Spawn child process with foreground control spawn('sleep 60', pgroup: false, preexec: method(:become_tty_fg)) Process.wait
Key Notes:
mkmfautomatically handles linking to libc, so the compiled.sowill have all necessary dependencies.- The extension adds
Process.tcsetpgrpandProcess.tcgetpgrpdirectly to Ruby's built-inProcessmodule for a native feel.
Critical Reminders
- Always run these operations in a child process's
preexeccallback (or immediately after fork) —setpgidandtcsetpgrpcan only modify the current process or unexecuted child processes. - Opening
/dev/ttyensures you're interacting with the controlling terminal, not a redirected stdin/stdout.
内容的提问来源于stack exchange,提问作者user3286792




