2ce399dc7b1845371c3ec323a695afd983c88464
[linux-2.6.git] / kernel / ckrm / ckrm_numtasks.c
1 /* ckrm_numtasks.c - "Number of tasks" resource controller for CKRM
2  *
3  * Copyright (C) Chandra Seetharaman,  IBM Corp. 2003
4  * 
5  * Latest version, more details at http://ckrm.sf.net
6  * 
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  */
13
14 /* Changes
15  * 
16  * 31 Mar 2004: Created
17  * 
18  */
19
20 /*
21  * Code Description: TBD
22  */
23
24 #include <linux/config.h>
25 #include <linux/module.h>
26 #include <linux/init.h>
27 #include <linux/slab.h>
28 #include <asm/errno.h>
29 #include <asm/div64.h>
30 #include <linux/list.h>
31 #include <linux/spinlock.h>
32 #include <linux/parser.h>
33 #include <linux/ckrm_rc.h>
34 #include <linux/ckrm_tc.h>
35 #include <linux/ckrm_tsk.h>
36
37 #define DEF_TOTAL_NUM_TASKS (131072)    // 128 K
38 #define DEF_FORKRATE (1000000)                  // 1 million tasks
39 #define DEF_FORKRATE_INTERVAL (3600)    // per hour
40 #define NUMTASKS_DEBUG
41 #define NUMTASKS_NAME "numtasks"
42 #define SYS_TOTAL_TASKS "sys_total_tasks"
43 #define FORKRATE "forkrate"
44 #define FORKRATE_INTERVAL "forkrate_interval"
45
46 static int total_numtasks = DEF_TOTAL_NUM_TASKS;
47 static int total_cnt_alloc = 0;
48 static int forkrate = DEF_FORKRATE;
49 static int forkrate_interval = DEF_FORKRATE_INTERVAL;
50 static ckrm_core_class_t *root_core;
51
52 typedef struct ckrm_numtasks {
53         struct ckrm_core_class *core;   // the core i am part of...
54         struct ckrm_core_class *parent; // parent of the core above.
55         struct ckrm_shares shares;
56         spinlock_t cnt_lock;    // always grab parent's lock before child's
57         int cnt_guarantee;      // num_tasks guarantee in local units
58         int cnt_unused;         // has to borrow if more than this is needed
59         int cnt_limit;          // no tasks over this limit.
60         atomic_t cnt_cur_alloc; // current alloc from self
61         atomic_t cnt_borrowed;  // borrowed from the parent
62
63         int over_guarantee;     // turn on/off when cur_alloc goes 
64                                 // over/under guarantee
65
66         // internally maintained statictics to compare with max numbers
67         int limit_failures;     // # failures as request was over the limit
68         int borrow_sucesses;    // # successful borrows
69         int borrow_failures;    // # borrow failures
70
71         // Maximum the specific statictics has reached.
72         int max_limit_failures;
73         int max_borrow_sucesses;
74         int max_borrow_failures;
75
76         // Total number of specific statistics
77         int tot_limit_failures;
78         int tot_borrow_sucesses;
79         int tot_borrow_failures;
80
81         // fork rate fields
82         int forks_in_period;
83         unsigned long period_start;
84 } ckrm_numtasks_t;
85
86 struct ckrm_res_ctlr numtasks_rcbs;
87
88 /* Initialize rescls values
89  * May be called on each rcfs unmount or as part of error recovery
90  * to make share values sane.
91  * Does not traverse hierarchy reinitializing children.
92  */
93 static void numtasks_res_initcls_one(ckrm_numtasks_t * res)
94 {
95         res->shares.my_guarantee = CKRM_SHARE_DONTCARE;
96         res->shares.my_limit = CKRM_SHARE_DONTCARE;
97         res->shares.total_guarantee = CKRM_SHARE_DFLT_TOTAL_GUARANTEE;
98         res->shares.max_limit = CKRM_SHARE_DFLT_MAX_LIMIT;
99         res->shares.unused_guarantee = CKRM_SHARE_DFLT_TOTAL_GUARANTEE;
100         res->shares.cur_max_limit = 0;
101
102         res->cnt_guarantee = CKRM_SHARE_DONTCARE;
103         res->cnt_unused = CKRM_SHARE_DONTCARE;
104         res->cnt_limit = CKRM_SHARE_DONTCARE;
105
106         res->over_guarantee = 0;
107
108         res->limit_failures = 0;
109         res->borrow_sucesses = 0;
110         res->borrow_failures = 0;
111
112         res->max_limit_failures = 0;
113         res->max_borrow_sucesses = 0;
114         res->max_borrow_failures = 0;
115
116         res->tot_limit_failures = 0;
117         res->tot_borrow_sucesses = 0;
118         res->tot_borrow_failures = 0;
119
120         res->forks_in_period = 0;
121         res->period_start = jiffies;
122
123         atomic_set(&res->cnt_cur_alloc, 0);
124         atomic_set(&res->cnt_borrowed, 0);
125         return;
126 }
127
128 #if 0
129 static void numtasks_res_initcls(void *my_res)
130 {
131         ckrm_numtasks_t *res = my_res;
132
133         /* Write a version which propagates values all the way down 
134            and replace rcbs callback with that version */
135
136 }
137 #endif
138
139 static int numtasks_get_ref_local(void *arg, int force)
140 {
141         int rc, resid = numtasks_rcbs.resid, borrowed = 0;
142         unsigned long now = jiffies, chg_at;
143         ckrm_numtasks_t *res;
144         ckrm_core_class_t *core = arg;
145
146         if ((resid < 0) || (core == NULL))
147                 return 1;
148
149         res = ckrm_get_res_class(core, resid, ckrm_numtasks_t);
150         if (res == NULL)
151                 return 1;
152
153 #ifdef CONFIG_CKRM_RES_NUMTASKS_FORKRATE
154         // force is not associated with fork. So, if force is specified
155         // we don't have to bother about forkrate.
156         if (!force) {
157                 // Take care of wraparound situation
158                 chg_at = res->period_start + forkrate_interval * HZ;
159                 if (chg_at < res->period_start) {
160                         chg_at += forkrate_interval * HZ;
161                         now += forkrate_interval * HZ;
162                 }
163                 if (chg_at <= now) {
164                         res->period_start = now;
165                         res->forks_in_period = 0;
166                 }
167         
168                 if (res->forks_in_period >= forkrate) {
169                         return 0;
170                 }
171         }
172 #endif
173
174         atomic_inc(&res->cnt_cur_alloc);
175
176         rc = 1;
177         if (((res->parent) && (res->cnt_unused == CKRM_SHARE_DONTCARE)) ||
178             (atomic_read(&res->cnt_cur_alloc) > res->cnt_unused)) {
179
180                 rc = 0;
181                 if (!force && (res->cnt_limit != CKRM_SHARE_DONTCARE) &&
182                     (atomic_read(&res->cnt_cur_alloc) > res->cnt_limit)) {
183                         res->limit_failures++;
184                         res->tot_limit_failures++;
185                 } else if (res->parent != NULL) {
186                         if ((rc =
187                              numtasks_get_ref_local(res->parent, force)) == 1) {
188                                 atomic_inc(&res->cnt_borrowed);
189                                 res->borrow_sucesses++;
190                                 res->tot_borrow_sucesses++;
191                                 res->over_guarantee = 1;
192                                 borrowed++;
193                         } else {
194                                 res->borrow_failures++;
195                                 res->tot_borrow_failures++;
196                         }
197                 } else {
198                         rc = force;
199                 }
200         } else if (res->over_guarantee) {
201                 res->over_guarantee = 0;
202
203                 if (res->max_limit_failures < res->limit_failures) {
204                         res->max_limit_failures = res->limit_failures;
205                 }
206                 if (res->max_borrow_sucesses < res->borrow_sucesses) {
207                         res->max_borrow_sucesses = res->borrow_sucesses;
208                 }
209                 if (res->max_borrow_failures < res->borrow_failures) {
210                         res->max_borrow_failures = res->borrow_failures;
211                 }
212                 res->limit_failures = 0;
213                 res->borrow_sucesses = 0;
214                 res->borrow_failures = 0;
215         }
216
217         if (!rc) {
218                 atomic_dec(&res->cnt_cur_alloc);
219         } else if (!borrowed) { 
220                 total_cnt_alloc++;
221 #ifdef CONFIG_CKRM_RES_NUMTASKS_FORKRATE
222                 if (!force) { // force is not associated with a real fork.
223                         res->forks_in_period++;
224                 }
225 #endif
226         }
227         return rc;
228 }
229
230 static void numtasks_put_ref_local(void *arg)
231 {
232         int resid = numtasks_rcbs.resid;
233         ckrm_numtasks_t *res;
234         ckrm_core_class_t *core = arg;
235
236         if ((resid == -1) || (core == NULL)) {
237                 return;
238         }
239
240         res = ckrm_get_res_class(core, resid, ckrm_numtasks_t);
241         if (res == NULL)
242                 return;
243         if (unlikely(atomic_read(&res->cnt_cur_alloc) == 0)) {
244                 printk(KERN_WARNING "numtasks_put_ref: Trying to decrement "
245                                         "counter below 0\n");
246                 return;
247         }
248         atomic_dec(&res->cnt_cur_alloc);
249         if (atomic_read(&res->cnt_borrowed) > 0) {
250                 atomic_dec(&res->cnt_borrowed);
251                 numtasks_put_ref_local(res->parent);
252         } else {
253                 total_cnt_alloc--;
254         }
255                 
256         return;
257 }
258
259 static void *numtasks_res_alloc(struct ckrm_core_class *core,
260                                 struct ckrm_core_class *parent)
261 {
262         ckrm_numtasks_t *res;
263
264         res = kmalloc(sizeof(ckrm_numtasks_t), GFP_ATOMIC);
265
266         if (res) {
267                 memset(res, 0, sizeof(ckrm_numtasks_t));
268                 res->core = core;
269                 res->parent = parent;
270                 numtasks_res_initcls_one(res);
271                 res->cnt_lock = SPIN_LOCK_UNLOCKED;
272                 if (parent == NULL) {
273                         // I am part of root class. So set the max tasks 
274                         // to available default
275                         res->cnt_guarantee = total_numtasks;
276                         res->cnt_unused = total_numtasks;
277                         res->cnt_limit = total_numtasks;
278                         root_core = core; // store the root core.
279                 }
280                 try_module_get(THIS_MODULE);
281         } else {
282                 printk(KERN_ERR
283                        "numtasks_res_alloc: failed GFP_ATOMIC alloc\n");
284         }
285         return res;
286 }
287
288 /*
289  * No locking of this resource class object necessary as we are not
290  * supposed to be assigned (or used) when/after this function is called.
291  */
292 static void numtasks_res_free(void *my_res)
293 {
294         ckrm_numtasks_t *res = my_res, *parres, *childres;
295         ckrm_core_class_t *child = NULL;
296         int i, borrowed, maxlimit, resid = numtasks_rcbs.resid;
297
298         if (!res)
299                 return;
300
301         // Assuming there will be no children when this function is called
302
303         parres = ckrm_get_res_class(res->parent, resid, ckrm_numtasks_t);
304
305         if (unlikely(atomic_read(&res->cnt_cur_alloc) < 0)) {
306                 printk(KERN_WARNING "numtasks_res: counter below 0\n");
307         }
308         if (unlikely(atomic_read(&res->cnt_cur_alloc) > 0 ||
309                                 atomic_read(&res->cnt_borrowed) > 0)) {
310                 printk(KERN_WARNING "numtasks_res_free: resource still "
311                        "alloc'd %p\n", res);
312                 if ((borrowed = atomic_read(&res->cnt_borrowed)) > 0) {
313                         for (i = 0; i < borrowed; i++) {
314                                 numtasks_put_ref_local(parres->core);
315                         }
316                 }
317         }
318         // return child's limit/guarantee to parent node
319         spin_lock(&parres->cnt_lock);
320         child_guarantee_changed(&parres->shares, res->shares.my_guarantee, 0);
321
322         // run thru parent's children and get the new max_limit of the parent
323         ckrm_lock_hier(parres->core);
324         maxlimit = 0;
325         while ((child = ckrm_get_next_child(parres->core, child)) != NULL) {
326                 childres = ckrm_get_res_class(child, resid, ckrm_numtasks_t);
327                 if (maxlimit < childres->shares.my_limit) {
328                         maxlimit = childres->shares.my_limit;
329                 }
330         }
331         ckrm_unlock_hier(parres->core);
332         if (parres->shares.cur_max_limit < maxlimit) {
333                 parres->shares.cur_max_limit = maxlimit;
334         }
335
336         spin_unlock(&parres->cnt_lock);
337         kfree(res);
338         module_put(THIS_MODULE);
339         return;
340 }
341
342 static inline int
343 do_share_calc(int a, int b, int c)
344 {
345         u64 temp;
346         if (a < 0) {
347                 temp = b;
348         } else {
349                 temp = (u64) a * b;
350         }
351         do_div(temp, c);
352         return (int) temp;
353 }
354
355 /*
356  * Recalculate the guarantee and limit in real units... and propagate the
357  * same to children.
358  * Caller is responsible for protecting res and for the integrity of parres
359  */
360 static void
361 recalc_and_propagate(ckrm_numtasks_t * res, ckrm_numtasks_t * parres)
362 {
363         ckrm_core_class_t *child = NULL;
364         ckrm_numtasks_t *childres;
365         int resid = numtasks_rcbs.resid;
366
367         if (parres) {
368                 struct ckrm_shares *par = &parres->shares;
369                 struct ckrm_shares *self = &res->shares;
370
371                 // calculate cnt_guarantee and cnt_limit
372                 //
373                 if ((parres->cnt_guarantee == CKRM_SHARE_DONTCARE) ||
374                     (self->my_guarantee == CKRM_SHARE_DONTCARE))
375                 {
376                         res->cnt_guarantee = CKRM_SHARE_DONTCARE;
377                 } else if (par->total_guarantee) {
378                         res->cnt_guarantee = 
379                                 do_share_calc(self->my_guarantee, 
380                                               parres->cnt_guarantee,
381                                               par->total_guarantee);
382                 } else {
383                         res->cnt_guarantee = 0;
384                 }
385
386                 if ((parres->cnt_limit == CKRM_SHARE_DONTCARE) ||
387                     (self->my_limit == CKRM_SHARE_DONTCARE)) {
388                         res->cnt_limit = CKRM_SHARE_DONTCARE;
389                 } else if (par->max_limit) {
390                         res->cnt_limit = 
391                                 do_share_calc(self->my_limit, 
392                                               parres->cnt_limit,
393                                               par->max_limit);
394                 } else {
395                         res->cnt_limit = 0;
396                 }
397
398                 // Calculate unused units
399                 if ((res->cnt_guarantee == CKRM_SHARE_DONTCARE) ||
400                     (self->my_guarantee == CKRM_SHARE_DONTCARE)) {
401                         res->cnt_unused = CKRM_SHARE_DONTCARE;
402                 } else if (self->total_guarantee) {
403                         res->cnt_unused = 
404                                 do_share_calc(self->unused_guarantee, 
405                                               res->cnt_guarantee,
406                                               par->total_guarantee);
407                 } else {
408                         res->cnt_unused = 0;
409                 }
410         }
411         // propagate to children
412         ckrm_lock_hier(res->core);
413         while ((child = ckrm_get_next_child(res->core, child)) != NULL) {
414                 childres = ckrm_get_res_class(child, resid, ckrm_numtasks_t);
415                 if (childres) {
416                     spin_lock(&childres->cnt_lock);
417                     recalc_and_propagate(childres, res);
418                     spin_unlock(&childres->cnt_lock);
419                 } else {
420                         printk(KERN_ERR "%s: numtasks resclass missing\n",__FUNCTION__);
421                 }
422         }
423         ckrm_unlock_hier(res->core);
424         return;
425 }
426
427 static int numtasks_set_share_values(void *my_res, struct ckrm_shares *new)
428 {
429         ckrm_numtasks_t *parres, *res = my_res;
430         struct ckrm_shares *cur = &res->shares, *par;
431         int rc = -EINVAL, resid = numtasks_rcbs.resid;
432
433         if (!res)
434                 return rc;
435
436         if (res->parent) {
437                 parres =
438                     ckrm_get_res_class(res->parent, resid, ckrm_numtasks_t);
439                 spin_lock(&parres->cnt_lock);
440                 spin_lock(&res->cnt_lock);
441                 par = &parres->shares;
442         } else {
443                 spin_lock(&res->cnt_lock);
444                 par = NULL;
445                 parres = NULL;
446         }
447
448         rc = set_shares(new, cur, par);
449
450         if ((rc == 0) && parres) {
451                 // Calculate parent's unused units
452                 if (parres->cnt_guarantee == CKRM_SHARE_DONTCARE) {
453                         parres->cnt_unused = CKRM_SHARE_DONTCARE;
454                 } else if (par->total_guarantee) {
455                         parres->cnt_unused =
456                                 do_share_calc(par->unused_guarantee, 
457                                               parres->cnt_guarantee,
458                                               par->total_guarantee);
459                 } else {
460                         parres->cnt_unused = 0;
461                 }
462                 recalc_and_propagate(res, parres);
463         }
464         spin_unlock(&res->cnt_lock);
465         if (res->parent) {
466                 spin_unlock(&parres->cnt_lock);
467         }
468         return rc;
469 }
470
471 static int numtasks_get_share_values(void *my_res, struct ckrm_shares *shares)
472 {
473         ckrm_numtasks_t *res = my_res;
474
475         if (!res)
476                 return -EINVAL;
477         *shares = res->shares;
478         return 0;
479 }
480
481 static int numtasks_get_stats(void *my_res, struct seq_file *sfile)
482 {
483         ckrm_numtasks_t *res = my_res;
484
485         if (!res)
486                 return -EINVAL;
487
488         seq_printf(sfile, "Number of tasks resource:\n");
489         seq_printf(sfile, "Total Over limit failures: %d\n",
490                    res->tot_limit_failures);
491         seq_printf(sfile, "Total Over guarantee sucesses: %d\n",
492                    res->tot_borrow_sucesses);
493         seq_printf(sfile, "Total Over guarantee failures: %d\n",
494                    res->tot_borrow_failures);
495
496         seq_printf(sfile, "Maximum Over limit failures: %d\n",
497                    res->max_limit_failures);
498         seq_printf(sfile, "Maximum Over guarantee sucesses: %d\n",
499                    res->max_borrow_sucesses);
500         seq_printf(sfile, "Maximum Over guarantee failures: %d\n",
501                    res->max_borrow_failures);
502 #ifdef NUMTASKS_DEBUG
503         seq_printf(sfile,
504                    "cur_alloc %d; borrowed %d; cnt_guar %d; cnt_limit %d "
505                    "cnt_unused %d, unused_guarantee %d, cur_max_limit %d\n",
506                    atomic_read(&res->cnt_cur_alloc),
507                    atomic_read(&res->cnt_borrowed), res->cnt_guarantee,
508                    res->cnt_limit, res->cnt_unused,
509                    res->shares.unused_guarantee,
510                    res->shares.cur_max_limit);
511 #endif
512
513         return 0;
514 }
515
516 static int numtasks_show_config(void *my_res, struct seq_file *sfile)
517 {
518         ckrm_numtasks_t *res = my_res;
519
520         if (!res)
521                 return -EINVAL;
522
523         seq_printf(sfile, "res=%s,%s=%d,%s=%d,%s=%d\n", NUMTASKS_NAME,
524                         SYS_TOTAL_TASKS, total_numtasks,
525                         FORKRATE, forkrate,
526                         FORKRATE_INTERVAL, forkrate_interval);
527         return 0;
528 }
529
530 enum numtasks_token_t {
531         numtasks_token_total,
532         numtasks_token_forkrate,
533         numtasks_token_interval,
534         numtasks_token_err
535 };
536
537 static match_table_t numtasks_tokens = {
538         {numtasks_token_total, SYS_TOTAL_TASKS "=%d"},
539         {numtasks_token_forkrate, FORKRATE "=%d"},
540         {numtasks_token_interval, FORKRATE_INTERVAL "=%d"},
541         {numtasks_token_err, NULL},
542 };
543
544 static void reset_forkrates(ckrm_core_class_t *parent, unsigned long now)
545 {
546         ckrm_numtasks_t *parres;
547         ckrm_core_class_t *child = NULL;
548
549         parres = ckrm_get_res_class(parent, numtasks_rcbs.resid,
550                                  ckrm_numtasks_t);
551         if (!parres) {
552                 return;
553         }
554         parres->forks_in_period = 0;
555         parres->period_start = now;
556
557         ckrm_lock_hier(parent);
558         while ((child = ckrm_get_next_child(parent, child)) != NULL) {
559                 reset_forkrates(child, now);
560         }
561         ckrm_unlock_hier(parent);
562 }
563
564 static int numtasks_set_config(void *my_res, const char *cfgstr)
565 {
566         char *p;
567         ckrm_numtasks_t *res = my_res;
568         int new_total, fr = 0, itvl = 0, err = 0;
569
570         if (!res)
571                 return -EINVAL;
572
573         while ((p = strsep((char**)&cfgstr, ",")) != NULL) {
574                 substring_t args[MAX_OPT_ARGS];
575                 int token;
576                 if (!*p)
577                         continue;
578
579                 token = match_token(p, numtasks_tokens, args);
580                 switch (token) {
581                 case numtasks_token_total:
582                         if (match_int(args, &new_total) ||
583                                                 (new_total < total_cnt_alloc)) {
584                                 err = -EINVAL;
585                         } else {
586                                 total_numtasks = new_total;
587                         
588                                 // res is the default class, as config is present only
589                                 // in that directory
590                                 spin_lock(&res->cnt_lock);
591                                 res->cnt_guarantee = total_numtasks;
592                                 res->cnt_unused = total_numtasks;
593                                 res->cnt_limit = total_numtasks;
594                                 recalc_and_propagate(res, NULL);
595                                 spin_unlock(&res->cnt_lock);
596                         }
597                         break;
598                 case numtasks_token_forkrate:
599                         if (match_int(args, &fr) || (fr <= 0)) {
600                                 err = -EINVAL;
601                         } else {
602                                 forkrate = fr;
603                         }
604                         break;
605                 case numtasks_token_interval:
606                         if (match_int(args, &itvl) || (itvl <= 0)) {
607                                 err = -EINVAL;
608                         } else {
609                                 forkrate_interval = itvl;
610                         }
611                         break;
612                 default:
613                         err = -EINVAL;
614                 }
615         }
616         if ((fr > 0) || (itvl > 0)) {
617                 reset_forkrates(root_core, jiffies);
618         }
619         return err;
620 }
621
622 static void numtasks_change_resclass(void *task, void *old, void *new)
623 {
624         ckrm_numtasks_t *oldres = old;
625         ckrm_numtasks_t *newres = new;
626
627         if (oldres != (void *)-1) {
628                 struct task_struct *tsk = task;
629                 if (!oldres) {
630                         struct ckrm_core_class *old_core =
631                             &(tsk->parent->taskclass->core);
632                         oldres =
633                             ckrm_get_res_class(old_core, numtasks_rcbs.resid,
634                                                ckrm_numtasks_t);
635                 }
636                 numtasks_put_ref_local(oldres->core);
637         }
638         if (newres) {
639                 (void)numtasks_get_ref_local(newres->core, 1);
640         }
641 }
642
643 struct ckrm_res_ctlr numtasks_rcbs = {
644         .res_name = NUMTASKS_NAME,
645         .res_hdepth = 1,
646         .resid = -1,
647         .res_alloc = numtasks_res_alloc,
648         .res_free = numtasks_res_free,
649         .set_share_values = numtasks_set_share_values,
650         .get_share_values = numtasks_get_share_values,
651         .get_stats = numtasks_get_stats,
652         .show_config = numtasks_show_config,
653         .set_config = numtasks_set_config,
654         .change_resclass = numtasks_change_resclass,
655 };
656
657 int __init init_ckrm_numtasks_res(void)
658 {
659         struct ckrm_classtype *clstype;
660         int resid = numtasks_rcbs.resid;
661
662         clstype = ckrm_find_classtype_by_name("taskclass");
663         if (clstype == NULL) {
664                 printk(KERN_INFO " Unknown ckrm classtype<taskclass>");
665                 return -ENOENT;
666         }
667
668         if (resid == -1) {
669                 resid = ckrm_register_res_ctlr(clstype, &numtasks_rcbs);
670                 printk(KERN_DEBUG "........init_ckrm_numtasks_res -> %d\n", resid);
671                 if (resid != -1) {
672                         ckrm_numtasks_register(numtasks_get_ref_local,
673                                                numtasks_put_ref_local);
674                         numtasks_rcbs.classtype = clstype;
675                 }
676         }
677         return 0;
678 }
679
680 void __exit exit_ckrm_numtasks_res(void)
681 {
682         if (numtasks_rcbs.resid != -1) {
683                 ckrm_numtasks_register(NULL, NULL);
684         }
685         ckrm_unregister_res_ctlr(&numtasks_rcbs);
686         numtasks_rcbs.resid = -1;
687 }
688
689 module_init(init_ckrm_numtasks_res)
690     module_exit(exit_ckrm_numtasks_res)
691
692     MODULE_LICENSE("GPL");