Hello, Before posting this on the Linux kernel mailing list I'd like to have your opinion about the relay fork module. This module sends a signal to one or several processes when a fork occurs in the kernel. It relays information about forks. It uses a small patch which is a hook in the kernel. I paste the kernel patch and the module at the end of this mail. I tested it on a kernel 2.6.10 Thanks in advance for all comments. Details about the module: ------------------------- The relay fork module is configured by using the sysfs. The main directory is called "/sys/relayfork" and it contains two files. One keeps the number of the signal that is send when a fork occurred and the other is a list of processes that are registered. Those files are writable. The tree structure is as follow: /sys/ | --> relayfork/ | ---> processes ---> signal If you want to register a process, just "echoing" its PID in the file "processes". Example: - Register process 123 echo 123 > /sys/relayfork/processes - Un-Register process 123 echo -123 > /sys/relayfork/processes You must be root to add a process in the list. By default the module sends the RT signal "33". We use a RT signal because if a signal occurs when an another one is still in the queue it is added. If it's not a RT signal, the signal is lost. The administrator is free to change it. echo XX > /sys/relayfork/signal This module uses a hook in the kernel tree. Thus you need to apply the corresponding patch in order to use it. ----8<--- the kernel patch ---- --- linux-2.6.10/kernel/fork.c.orig 2005-01-04 11:28:58.000000000 +0100 +++ linux-2.6.10/kernel/fork.c 2005-01-04 13:23:24.000000000 +0100 @@ -61,6 +61,52 @@ rwlock_t tasklist_lock __cacheline_align EXPORT_SYMBOL(tasklist_lock); +/* + * fork_hook is a pointer to a function to call when forking if it + * isn't NULL. + */ +void (*fork_hook) (int, int) = NULL; + +/** + * trace_fork: call a given external function when forking + * @func: function to call + * @id: identifier of fork_hook's owner + * + * This function sets the fork_hook and makes some sanity checks. + * Function returns 0 on success, -1 on failure (ie hook is + * already used). + */ +int trace_fork(void (*func) (int, int), int id) +{ + /* + * fork_hook_id is used as a lock to protect the use of + * fork_hook variable. A module can set the fork_hook + * variable if it's not already used (fork_hook_id == 0) + * or if it has the corresponding fork_hook_id. + * We use a static variable to keep its last value. + */ + static int fork_hook_id = 0; + + /* We can set the hook if it's not already used */ + if ((func != NULL) && (fork_hook_id == 0)) { + fork_hook = func; + fork_hook_id = id; + return 0; + } + + /* We can remove the hook if we are the owner */ + if ((func == NULL) && (fork_hook_id == id)) { + fork_hook = NULL; + fork_hook_id = 0; + return 0; + } + + /* Request can not be satisfied */ + return -1; +} + +EXPORT_SYMBOL(trace_fork); + int nr_processes(void) { int cpu; @@ -1168,6 +1214,10 @@ long do_fork(unsigned long clone_flags, free_pidmap(pid); pid = PTR_ERR(p); } + + if (fork_hook != NULL) + fork_hook(current->pid, pid); + return pid; } --------8< relay_fork.c module ---------------------- /* * Relay fork interface * * Author: Guillaume Thouvenin <guillaume.thouvenin@xxxxxxxx> * * This module is a relay for fork monitoring. You use the * /sys/relayfork/processes to register your process or to * display registered processes. * The interface send a signal (default is a the RT signal 33) * to all registered processes when a fork occurs in the kernel. * The signal can be change by using /sys/relayfork/signal. * * Conventions: * structures are prefixed by rfork_ * functions are prefixed by rfd_ * variable are prefixed by relayf_ */ #include <linux/module.h> /* for all modules */ #include <linux/kernel.h> /* for KERN_ALERT */ #include <linux/init.h> /* for the macros */ #include <linux/spinlock.h> /* for spinlock */ #include <linux/sched.h> /* for task_struct */ #include <linux/kobject.h> #define RELAY_FORK_SIGNAL 33 #define TRACE_FORK_ID 3538 MODULE_AUTHOR("Guillaume Thouvenin <guillaume.thouvenin@xxxxxxxx>"); MODULE_DESCRIPTION("sysfs interface to relay fork device"); MODULE_LICENSE("GPL"); /* available if the relay-fork patch is applied */ extern int trace_fork(void (*func) (int, int), int id); /******************* * local definition *******************/ struct rfork_device { struct kobject kobj; struct list_head proclist; /* registered processes */ unsigned long signal; /* signal to send to processes */ }; struct rfork_attribute { struct attribute attr; ssize_t(*show) (struct rfork_device * dev, char *buf); ssize_t(*store) (struct rfork_device * dev, const char *buf, size_t count); }; struct rfork_proclist { struct list_head list; unsigned long pid; }; /***************************** * header for local functions *****************************/ ssize_t rfd_attr_show(struct kobject *kobj, struct attribute *attr, char *buf); ssize_t rfd_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t size); ssize_t rfd_proclist_show(struct rfork_device *dev, char *buf); ssize_t rfd_proclist_store(struct rfork_device *dev, const char *buf, size_t size); ssize_t rfd_signal_show(struct rfork_device *dev, char *buf); ssize_t rfd_signal_store(struct rfork_device *dev, const char *buf, size_t size); /***************** * local variable *****************/ static spinlock_t relayf_lock = SPIN_LOCK_UNLOCKED; /* protect relayf */ struct sysfs_ops relayf_sysfs_ops = { .show = rfd_attr_show, .store = rfd_attr_store, }; struct kobj_type relayf_kobj_type = { .sysfs_ops = &relayf_sysfs_ops, }; static struct rfork_device relayf; #define RELAYF_ATTR(_name,_mode,_show,_store) \ struct rfork_attribute relayf_attr_##_name = { \ .attr = {.name = __stringify(_name) , .mode = _mode }, \ .show = _show, \ .store = _store, \ }; /* give write access only to root, otherwise it's a security hole */ static RELAYF_ATTR(processes, 0644, rfd_proclist_show, rfd_proclist_store); static RELAYF_ATTR(signal, 0644, rfd_signal_show, rfd_signal_store); /************************** * body of local functions **************************/ /** * rfd_strtoi - convert a string to an unsigned long * @name: the string to convert * * Convert a string to an unsigned long. When we found * a character that is not a number, we return immediatly. * The first character can be '-' but if it is, we just * ignore it and return the positive value. */ static unsigned long rfd_strtoi(const char *name) { unsigned long val = 0; /* If it's a negative value we just forgot the '-' */ if (*name == '-') name++; for (;; name++) { switch (*name) { case '0'...'9': val = 10 * val + (*name - '0'); break; default: return val; } } } /** * rfd_nbdigits - return the number of digits of an integer * @i: the integer * * example: For "0" it returns 1 * For "324" it returns 3 * For "-324" it returns 3 */ static inline int rfd_nbdigits(int i) { int res = 0; if (i < 0) i *= -1; do { i = i / 10; res++; } while (i != 0); return res; } #define KOBJ_TO_RFORK(obj) container_of(obj, struct rfork_device, kobj) #define ATTR_TO_RFORK(obj) container_of(obj, struct rfork_attribute, attr) /** * rfd_attr_show - method called when a file is read * @kobj: the kobject * @attr: the attribute * @buf: the buffer * * Translate the generic struct kobject and struct attribute * pointers to the appropriate pointer types, and calls the * associated methods. */ ssize_t rfd_attr_show(struct kobject * kobj, struct attribute * attr, char *buf) { struct rfork_device *rf_dev = KOBJ_TO_RFORK(kobj); struct rfork_attribute *rf_attr = ATTR_TO_RFORK(attr); ssize_t ret = 0; if (rf_attr->show) ret = rf_attr->show(rf_dev, buf); return ret; } /** * rfd_attr_store - method called when a file is written * @kobj: the kobject * @attr: the attribute * @buf: the buffer * * Translate the generic struct kobject and struct attribute * pointers to the appropriate pointer types, and calls the * associated methods. */ ssize_t rfd_attr_store(struct kobject * kobj, struct attribute * attr, const char *buf, size_t size) { struct rfork_device *rf_dev = KOBJ_TO_RFORK(kobj); struct rfork_attribute *rf_attr = ATTR_TO_RFORK(attr); ssize_t ret = 0; if (rf_attr->store) ret = rf_attr->store(rf_dev, buf, size); return ret; } /** * rfd_proclist_show - method called when processes attribute is read * @dev: the relay fork device * @buf: the buffer * * The buffer is a buffer of size PAGE_SIZE (due to sysfs implementation). * As the method is called only once for a read, the show() method should * fill the entire buffer. Buffer is filled with a list of processes that * are registered. As the size of the buffer is limited, we also limit * how many processes are displayed. * Method returns the number of bytes printed into the buffer. If a bad * value comes through, it returns an error. */ ssize_t rfd_proclist_show(struct rfork_device * dev, char *buf) { struct rfork_proclist *p; struct list_head *pos; struct list_head *next; int char_print; char tmp[PAGE_SIZE]; char_print = 0; spin_lock_irq(&relayf_lock); list_for_each_safe(pos, next, &dev->proclist) { p = container_of(pos, struct rfork_proclist, list); if (char_print + rfd_nbdigits(p->pid) + 1 < PAGE_SIZE - 1) { memset(tmp, 0, PAGE_SIZE); char_print += sprintf(tmp, "%lu ", p->pid); strcat(buf, tmp); } else /* * buf is full enough, we don't need to go through * the list */ break; } spin_unlock_irq(&relayf_lock); strncat(buf, "\n", 1); char_print++; return char_print; } /** * rfd_proclist_store - method called when processes attribute is written * @dev: the relay fork device * @buf: the buffer * @size: * * As the method is called only once for a write, the store() method should * fill the entire buffer. Methods should return the number of bytes used * from the buffer. If a bad value comes through, we return an error. */ ssize_t rfd_proclist_store(struct rfork_device * dev, const char *buf, size_t size) { struct rfork_proclist *p; struct list_head *pos; /* use as a loop counter */ struct list_head *next; /* temporary storage */ int pid; /* process to add or remove */ pid = rfd_strtoi(buf); if (pid == 0) /* nothing to do */ return strlen(buf); switch (*buf) { case '-': /* remove the process */ spin_lock_irq(&relayf_lock); list_for_each_safe(pos, next, &dev->proclist) { p = container_of(pos, struct rfork_proclist, list); if (p->pid == pid) { list_del(&p->list); spin_unlock_irq(&relayf_lock); kfree(p); return strlen(buf); } } spin_unlock_irq(&relayf_lock); break; default: /* add the process. We check if it is already in the list */ spin_lock_irq(&relayf_lock); list_for_each_safe(pos, next, &dev->proclist) { p = container_of(pos, struct rfork_proclist, list); if (p->pid == pid) { spin_unlock_irq(&relayf_lock); return strlen(buf); } } /* check if it is a valid pid */ read_lock(&tasklist_lock); if (!find_task_by_pid(pid)) { read_unlock(&tasklist_lock); spin_unlock_irq(&relayf_lock); return strlen(buf); } read_unlock(&tasklist_lock); spin_unlock_irq(&relayf_lock); p = kmalloc(sizeof(struct rfork_proclist), GFP_KERNEL); if (!p) return -ENOMEM; INIT_LIST_HEAD(&p->list); p->pid = rfd_strtoi(buf); spin_lock_irq(&relayf_lock); list_add(&p->list, &dev->proclist); spin_unlock_irq(&relayf_lock); } return strlen(buf); } /** * rfd_signal_show - method called when signal attribute is read * @dev: the relay fork device * @buf: the buffer * * Buffer is filled by an integer which is the RT signal used by * the relay fork interface to relay fork information to processes. */ ssize_t rfd_signal_show(struct rfork_device * dev, char *buf) { return sprintf(buf, "%lu\n", dev->signal); } /** * rfd_signal_store - method called when signal attribute is written * @dev: the relay fork device * @buf: the buffer * @size: * * Read the string in the buffer and convert it to an unsigned long * integer. This value is used to set the new RT signal. A RT signal * must be between SIGRTMIN and SIGRTMAX. */ ssize_t rfd_signal_store(struct rfork_device * dev, const char *buf, size_t size) { unsigned long rtsignal = rfd_strtoi(buf); spin_lock_irq(&relayf_lock); if ((rtsignal > SIGRTMIN) && (rtsignal < SIGRTMAX)) dev->signal = rfd_strtoi(buf); spin_unlock_irq(&relayf_lock); return strlen(buf); } /** * rfd_relay_fork_info - send information to all registered processes * @parent: PID of the parent * @child: PID of the child * * Send information to all registered processes when a fork * occurs in the kernel. */ void rfd_relay_fork_info(int parent, int child) { struct list_head *pos; struct list_head *next; struct siginfo sinfo; /*pr_info("FORK: parent = %d, child = %d\n", parent, child); */ spin_lock_irq(&relayf_lock); list_for_each_safe(pos, next, &relayf.proclist) { struct task_struct *taskp; struct rfork_proclist *p = container_of(pos, struct rfork_proclist, list); read_lock(&tasklist_lock); taskp = find_task_by_pid(p->pid); if (taskp) { sinfo.si_signo = relayf.signal; sinfo.si_errno = 0; sinfo.si_code = SI_ASYNCIO; /* * should be the sender ID but in our case, it is set * to the pid of the process that initiates the fork. */ sinfo.si_pid = parent; /* * process created during the fork. This information * is needed to keep jobs coherent. */ sinfo.si_value.sival_int = child; send_sig_info(relayf.signal, &sinfo, taskp); } else { /* we can remove it from proclist */ list_del(&p->list); kfree(p); } read_unlock(&tasklist_lock); } spin_unlock_irq(&relayf_lock); } /** * Modules start and stop **/ /** * rfd_init - */ static int __init rfd_init(void) { int retval; retval = trace_fork(&rfd_relay_fork_info, TRACE_FORK_ID); if (retval < 0) return retval; /* Initialization of relayf */ kobject_set_name(&relayf.kobj, "relayfork"); relayf.kobj.ktype = &relayf_kobj_type; INIT_LIST_HEAD(&relayf.proclist); relayf.signal = RELAY_FORK_SIGNAL; /* * Register the kobject. As our object doesn't have a parent * or a dominant kset, a directory is created at the top-level * of the sysfs partition. */ retval = kobject_register(&relayf.kobj); sysfs_create_file(&relayf.kobj, &relayf_attr_processes.attr); sysfs_create_file(&relayf.kobj, &relayf_attr_signal.attr); return retval; } /** * rfd_cleanup - */ static void __exit rfd_cleanup(void) { struct list_head *pos; /* use as a loop counter */ struct list_head *next; /* temporary storage */ trace_fork(NULL, TRACE_FORK_ID); /* release entry that was dynamically allocated */ list_for_each_safe(pos, next, &relayf.proclist) { struct rfork_proclist *p = container_of(pos, struct rfork_proclist, list); list_del(&p->list); kfree(p); } sysfs_remove_file(&relayf.kobj, &relayf_attr_processes.attr); sysfs_remove_file(&relayf.kobj, &relayf_attr_signal.attr); kobject_unregister(&relayf.kobj); } module_init(rfd_init); module_exit(rfd_cleanup); -- Kernelnewbies: Help each other learn about the Linux kernel. Archive: http://mail.nl.linux.org/kernelnewbies/ FAQ: http://kernelnewbies.org/faq/