2003-08-14 19:15:20

by Vladimir Kondratiev

[permalink] [raw]
Subject: resource leak in kernel_thread()

I need to spawn and terminate kernel thread from the module quite often.
At any given time, only one thread is alive. There are events that
triggers thread creation and termination.
I found that, if I create/terminate thread many times (3000 - 4000
times), system get to the stage when it can't create new process. I
checked that, if I terminate one (already running) process after getting
to this stage, I can create one more kernel thread, then system return
to the state when no more processes can be created.

I use vanilla 2.4.20 kernel.

kernel_thread() start returning -11, which is -EAGAIN.

I must be doing something stupid, but I looked at kernel thread usage in
the kernel code and found it is the same as in my example (to extent I
can understand :-) ).

I will appreciate any help WRT proper kernel_thread usage. Please, CC me
([email protected]) in your reply since I am not subscribed
to the list.

To simulate this situation, I created simple example. Following code is
module that creates/terminates thread. To model external event that will
trigger creation/termination, I use "/proc/kthread" file. The same file
used to get some statistics. To create thread, one need to write "+" to
the file, to terminate - "-". I run it on single CPU, so I removed all
kernel_lock() related code.

To execute the test, run the following script. After some 3000-4000
threads your system will be unable to create processes.

---kthread.sh begin---
#!/bin/sh
f="/proc/kthread"

function start () {
echo "+" > $f
}

function stop () {
echo "-" > $f
}

function display () {
clear
cat $f
}

sudo /sbin/insmod kthread.o
while true; do start; display; stop; done
---kthread.sh end---
---Makefile begin---
ifneq ($(KERNELRELEASE),)

obj-m := kthread.o

kthread-objs := kthread-main.o

include $(TOPDIR)/Rules.make

$(obj-m) : $($(obj-m:.o=-objs)) $(lib)
$(LD) -r -o $@ $($(obj-m:.o=-objs)) $(lib)

else

KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

default:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules

endif
---Makefile end---
---kthread-main.c begin---
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/proc_fs.h>
#include <linux/vmalloc.h>
#include <asm/uaccess.h>

typedef struct kt_priv_struct {
int nthreads; /**< let's count how many times kernel_thread succeeded */
int pid;
wait_queue_head_t wq; /**< kthread sleep here */
struct completion exited;
__u32 volatile status;
} kt_priv_t;

static kt_priv_t the_data;

static int kt_thread(void* arg)
{
kt_priv_t* priv=(kt_priv_t*)arg;
daemonize();
reparent_to_init();
snprintf(current->comm,sizeof(current->comm),"kth_%d",priv->nthreads);
++priv->nthreads;
while (!test_bit(0,&priv->status)) {
/* framework for periodical execution */
unsigned long to=HZ; /* timeout for periodical maintenance */
do {
to=interruptible_sleep_on_timeout(&priv->wq,to);
} while ( !signal_pending(current) && to>0 );
if (signal_pending(current)) {
spin_lock_irq(&current->sigmask_lock);
flush_signals(current);
spin_unlock_irq(&current->sigmask_lock);
}
/* thread body - for this simple example do nothing */
}
complete_and_exit(&priv->exited,0);
}

static void kt_start(kt_priv_t* priv)
{
if (priv->pid>0) return;
init_completion(&priv->exited);
init_waitqueue_head(&priv->wq);
priv->status=0;
priv->pid=kernel_thread(kt_thread,priv,0);
}

static void kt_stop(kt_priv_t* priv)
{
if (priv->pid>0 && (0==test_and_set_bit(0,&priv->status))) {
kill_proc(priv->pid,SIGHUP,1);
wait_for_completion(&priv->exited);
priv->pid=0;
}
}

static int kt_proc_read(char* buf,char** start,off_t offset,int
count,int* eof,void* data)
{
kt_priv_t* priv=(kt_priv_t*)data;
int len=0;
len+=sprintf(buf+len,"nthreads = %d\n",priv->nthreads);
len+=sprintf(buf+len,"pid = %d\n",priv->pid);
*eof=1;
return len;
}

static int kt_proc_write(struct file *file, const char *buffer,unsigned
long count, void *data)
{
kt_priv_t* priv=(kt_priv_t*)data;
int rc=count;
if (count<1) {
return -EINVAL;
}
char* buf=vmalloc(count+1);
if (!buf) return -ENOMEM;
buf[count]='\0';
if (copy_from_user(buf,buffer,count)) {
rc=-EFAULT;
goto out;
}
switch (buf[0]) {
case '+':
kt_start(priv);
break;
case '-':
kt_stop(priv);
break;
}
out:
vfree(buf);
return rc;
}

static char* kt_proc_name="kthread";

static int kt_mod_init(void)
{
kt_priv_t* priv=&the_data;
memset(priv,0,sizeof(*priv));
struct proc_dir_entry*
p=create_proc_read_entry(kt_proc_name,0666,NULL,kt_proc_read,priv);
if (p) {
SET_MODULE_OWNER(p);
p->write_proc=kt_proc_write;
}
return 0;
}

static void kt_mod_exit(void)
{
kt_stop(&the_data);
remove_proc_entry(kt_proc_name,NULL);
}

module_init(kt_mod_init);
module_exit(kt_mod_exit);

MODULE_AUTHOR("Vladimir Kondratiev <[email protected]>");
MODULE_DESCRIPTION("Kernel thread example");
MODULE_LICENSE("GPL");
---kthread-main.c end---