Моя цель — открыть устройство в одном процессе, а затем закрыть его в другом (не форк).
Я не хочу использовать сокет UNIX. Я обнаружил, что начиная с ядра 5.6 появилось два новых системных вызова: syscall(SYS_pidfd_open, pid, flags)
и syscall(SYS_pidfd_getfd, pidfd, 0, 0)
.
Я попробовал простой код, чтобы проверить это решение.
open_device.c:
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <errno.h>
#define pidfd_open(pid, flags) syscall(SYS_pidfd_open, pid, flags)
int main() {
// Open the device
int fd = open("/dev/null", O_RDWR);
if (fd == -1) {
perror("open");
return EXIT_FAILURE;
}
printf("File descriptor for /dev/null: %d\n", fd);
// Get the current process ID
pid_t pid = getpid();
// Obtain a pidfd for this process
int pidfd = pidfd_open(pid, 0);
if (pidfd == -1) {
perror("pidfd_open");
close(fd);
return EXIT_FAILURE;
}
printf("Generated pidfd: %d\n", pidfd);
// Write pidfd to a file
FILE *pidfd_file = fopen("pidfd.txt", "w");
if (pidfd_file == NULL) {
perror("fopen");
close(fd);
close(pidfd);
return EXIT_FAILURE;
}
fprintf(pidfd_file, "%d\n", pidfd);
fclose(pidfd_file);
// Keep the process alive for testing
while (1) {
sleep(10);
}
// Clean up
close(fd);
close(pidfd);
return 0;
}
close_device.c
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/syscall.h>
#include <errno.h>
#ifndef SYS_pidfd_getfd
#define SYS_pidfd_getfd 438 // Replace with the correct syscall number for your architecture
#endif
int main() {
// Read the pidfd from the file
FILE *pidfd_file = fopen("pidfd.txt", "r");
if (pidfd_file == NULL) {
perror("fopen");
return EXIT_FAILURE;
}
int pidfd;
if (fscanf(pidfd_file, "%d", &pidfd) != 1) {
perror("fscanf");
fclose(pidfd_file);
return EXIT_FAILURE;
}
fclose(pidfd_file);
printf("Read pidfd: %d\n", pidfd);
// Retrieve the file descriptor from the first process
int target_fd = syscall(SYS_pidfd_getfd, pidfd, 0, 0);
if (target_fd == -1) {
perror("pidfd_getfd");
close(pidfd);
return EXIT_FAILURE;
}
printf("Successfully retrieved fd: %d\n", target_fd);
// Perform the close operation
if (close(target_fd) == -1) {
perror("close");
close(pidfd);
return EXIT_FAILURE;
}
printf("Device successfully closed.\n");
close(pidfd);
return 0;
}
Проблема в том, что я получаю сообщение об ошибке Bad file descriptor
для pidfd_getfd
.
Где может быть проблема?
@Г.М. Прочтите документацию еще раз. ОП использует его правильно.
@stark Хорошо, я все еще не совсем понимаю, но с радостью подчинюсь другим :-)
@stark Нет, он неправильно используется в коде OP, как G.M. отметил. pidfd
должен быть дескриптор файла PID, а не дескриптор файла, который вы хотите дублировать.
Вы используете неправильные аргументы для SYS_pidfd_getfd
. pidfd
должен быть дескриптором PID, int pidfd = pidfd_open(remote_pid, 0);
, где remote_pid
— это идентификатор процесса, из которого вы хотите получить дубликат дескриптора файла.
Вы можете просто записать PID процесса открытия в тот же файл, а затем в close_device.c
прочитать его и затем вызвать pidfd_open
:
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#ifndef SYS_pidfd_getfd
# define SYS_pidfd_getfd \
438 // Replace with the correct syscall number for your architecture
#endif
#define pidfd_open(pid, flags) syscall(SYS_pidfd_open, pid, flags)
int main(void) {
// Read the pidfd from the file
FILE* pidfd_file = fopen("pidfd.txt", "r");
if (pidfd_file == NULL) {
perror("fopen");
return EXIT_FAILURE;
}
pid_t remote_pid;
int remote_fd;
// read both pid and the descriptor from the file:
// Note: %d may not be appropriate for `pid_t`
if (fscanf(pidfd_file, "%d %d", &remote_pid, &remote_fd) != 2) {
perror("fscanf");
fclose(pidfd_file);
return EXIT_FAILURE;
}
fclose(pidfd_file);
printf("Read pid:%d remote_fd: %d\n", remote_pid, remote_fd);
// now get the pidfd for the remote process:
int pidfd = pidfd_open(remote_pid, 0);
if (pidfd == -1) {
perror("pidfd_open");
return EXIT_FAILURE;
}
// Now you can retrieve the file descriptor from the remote process
int target_fd = syscall(SYS_pidfd_getfd, pidfd, remote_fd, 0);
if (target_fd == -1) {
perror("pidfd_getfd");
close(pidfd);
return EXIT_FAILURE;
}
printf("Successfully retrieved fd: %d\n", target_fd);
// Perform the close operation
if (close(target_fd) == -1) {
perror("close");
return EXIT_FAILURE;
}
printf("Device successfully closed.\n");
}
Спасибо за помощь! Теперь я получаю следующую ошибку: Read pid:259760 remote_fd: 4 pidfd_getfd: Operation not permitted
Ну, ядро вам сообщило... читали ли вы документацию, связанную с этими системными вызовами? (прочтение последнего абзаца моего ответа, вероятно, объяснит, почему вы получаете эту ошибку)
@Droll80 Droll80 У процесса должно быть разрешение на дублирование файлового дескриптора другого процесса. Вы запускаете оба процесса от одного и того же пользователя? «Разрешение на дублирование файлового дескриптора другого процесса регулируется проверкой ptrace
режима доступа PTRACE_MODE_ATTACH_REALCREDS
(см. ptrace(2)
)».
Дескрипторы файлов действительны в контексте процесса. Это позволяет каждому процессу в системе считать, что файловый дескриптор 0 связан со стандартным вводом, 1 со стандартным выводом и 2 со стандартной ошибкой, но это также добавляет возможность того, что они будут разными для разных процессов. Дескриптор файла — это частные виртуальные номера процесса, которые не имеют значения для другого процесса.
Таким образом, число вроде 18 не может быть файловым дескриптором системного ресурса в одном процессе и быть таким же числом в другом процессе (использовали вы fork или нет)
Единственным исключением из вышеизложенного является то, что система сохраняет (в дочернем процессе) те же значения дескриптора, которые были открыты в родительском процессе, в результате чего оба процесса имеют одинаковые значения для обоих процессов, но во всех других случаях это не может быть гарантировано. поскольку принимающий процесс уже может иметь такое же значение дескриптора для другого ресурса.
В любом случае, в системе есть функция, с помощью которой вы можете переименовать файловый дескриптор в другое неиспользуемое значение, это означает, что процесс может преобразовать файловый дескриптор 8 в 4. Это набор системных вызовов dup
и dup2
. Возможно, это то, чего вы хотите. Но поскольку вы не говорите и в коде четко не изложены ваши намерения, догадаться не представляется возможным.
Существуют способы передачи файловых дескрипторов между процессами, давно реализованные в BSD unix. Они были экспортированы в другие версии Unix, включая Linux (сейчас я не знаю, являются ли они вообще частью POSIX). Сокеты Unix позволяют передавать дескриптор файла другому процессу, но это операция PUSH (предполагает взаимодействие обоих процессов и не совсем та же семантика, которую пытается реализовать Linux - при которой один процесс не знает, что другой процесс обманул свой файловый дескриптор и может управлять файлом) Это может стать проблемой безопасности, если не обращаться с ним осторожно. Судя по некоторым обновлениям, которые вы внесли в свои комментарии, вы получаете сообщение о том, что операция запрещена, поэтому, вероятно, для этого потребуются некоторые атрибуты процесса, которых у вас нет. Я могу только предложить вам запустить промежуточный процесс как root
, чтобы проверить, так ли это. Но ИМХО, пока не будет полной документации по этим системным вызовам и они не будут полностью интегрированы/привязаны к стандартной библиотеке C, вам придется читать исходные коды ядра. Хорошей ссылкой может быть Отправка дескриптора файла через сокет unix.
Кстати, возможность дублировать (а это именно та операция, которую вы хотите) другого, несвязанного процесса — опасная проблема, поскольку два дескриптора ресурса будут вести себя так, как если бы дескриптор был dup
локально. Это заставит оба процесса совместно использовать флаги открытия файла и, что еще хуже, указатель файла, поэтому каждый раз, когда какой-либо процесс записывает (или читает) файл, указатель файла будет перемещаться, что, вероятно, нарушает доступ другого процесса к нему. файл. Этого никогда не произойдет, если вы снова откроете тот же ресурс. Системные вызовы, о которых вы говорите, опасны, непереносимы и должны использоваться с особой осторожностью.
Процесс заключается в том, чтобы передать дескриптор файла PID другому процессу, а затем использовать его для запроса дублирования дескриптора файла из этого процесса. Это было бы очень выполнимо, как я показал в своем ответе. Что касается редактирования: ОП хочет дублировать дескриптор файла из другого процесса. Это очень ясно. dup
/dup2
не очень помогает в настройке, описанной в вопросе. Не нужно гадать.
Для этого есть решения, но вы получаете новый дескриптор (в виде целого числа) в новом процессе, число не будет прежним. Вы можете сделать это через сокет unix. Дескриптор файла, который вы получаете, связан с тем же ресурсом в том же состоянии (как если бы вы сделали дублирование), но ядро не даст вам то же значение!!!
Вы отвечаете на вопрос или спрашиваете? Со мной не нужно дискутировать. Ошибка, которую допускает ОП, заключается в том, что они думают, что цифры будут одинаковыми.
Да, при дублировании файлового дескриптора вы получите уникальное значение для текущего процесса, которое связано с тем же ресурсом, что и файловый дескриптор в первом процессе. Это именно то, чего хочет ОП, но он пропустил шаг.
ОП совершает очень распространенную ошибку: думает, что дескрипторы глобальны и что он может получить доступ к дескриптору, если просто знает его номер. Он думает, что ему нужно получить такое же числовое значение. И мы с вами правы в том, что номер новый.
Ре. "The OP is ... thinking descriptors are global and that he can access the descriptor if he just knows the number"
: ОП так не думает. Извините, но этот ответ полностью упускает суть того, что пытается сделать ФП и чего можно достичь.
Просто чтобы уточнить: мне нужно использовать общий дескриптор файла между процессами. Можете ли вы посоветовать мне лучший способ сделать это?
используйте сокеты unix и передайте дескриптор файла (и, возможно, гораздо больше информации через хорошо разработанный протокол) через сокет unix (man unix)
Я раньше не использовал эти API, но вызов
syscall(SYS_pidfd_getfd, ...
выглядит немного странно. Согласно документации вторым параметром должен быть pid процесса открытия, а третьим параметром — целевой fd в этом процессе (если я неправильно понял). Это не то, что у вас есть.