Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v2.6.25-rc7 762 lines 19 kB view raw
1/* 2 * IPVS: Locality-Based Least-Connection with Replication scheduler 3 * 4 * Version: $Id: ip_vs_lblcr.c,v 1.11 2002/09/15 08:14:08 wensong Exp $ 5 * 6 * Authors: Wensong Zhang <wensong@gnuchina.org> 7 * 8 * This program is free software; you can redistribute it and/or 9 * modify it under the terms of the GNU General Public License 10 * as published by the Free Software Foundation; either version 11 * 2 of the License, or (at your option) any later version. 12 * 13 * Changes: 14 * Julian Anastasov : Added the missing (dest->weight>0) 15 * condition in the ip_vs_dest_set_max. 16 * 17 */ 18 19/* 20 * The lblc/r algorithm is as follows (pseudo code): 21 * 22 * if serverSet[dest_ip] is null then 23 * n, serverSet[dest_ip] <- {weighted least-conn node}; 24 * else 25 * n <- {least-conn (alive) node in serverSet[dest_ip]}; 26 * if (n is null) OR 27 * (n.conns>n.weight AND 28 * there is a node m with m.conns<m.weight/2) then 29 * n <- {weighted least-conn node}; 30 * add n to serverSet[dest_ip]; 31 * if |serverSet[dest_ip]| > 1 AND 32 * now - serverSet[dest_ip].lastMod > T then 33 * m <- {most conn node in serverSet[dest_ip]}; 34 * remove m from serverSet[dest_ip]; 35 * if serverSet[dest_ip] changed then 36 * serverSet[dest_ip].lastMod <- now; 37 * 38 * return n; 39 * 40 */ 41 42#include <linux/ip.h> 43#include <linux/module.h> 44#include <linux/kernel.h> 45#include <linux/skbuff.h> 46#include <linux/jiffies.h> 47 48/* for sysctl */ 49#include <linux/fs.h> 50#include <linux/sysctl.h> 51#include <net/net_namespace.h> 52 53#include <net/ip_vs.h> 54 55 56/* 57 * It is for garbage collection of stale IPVS lblcr entries, 58 * when the table is full. 59 */ 60#define CHECK_EXPIRE_INTERVAL (60*HZ) 61#define ENTRY_TIMEOUT (6*60*HZ) 62 63/* 64 * It is for full expiration check. 65 * When there is no partial expiration check (garbage collection) 66 * in a half hour, do a full expiration check to collect stale 67 * entries that haven't been touched for a day. 68 */ 69#define COUNT_FOR_FULL_EXPIRATION 30 70static int sysctl_ip_vs_lblcr_expiration = 24*60*60*HZ; 71 72 73/* 74 * for IPVS lblcr entry hash table 75 */ 76#ifndef CONFIG_IP_VS_LBLCR_TAB_BITS 77#define CONFIG_IP_VS_LBLCR_TAB_BITS 10 78#endif 79#define IP_VS_LBLCR_TAB_BITS CONFIG_IP_VS_LBLCR_TAB_BITS 80#define IP_VS_LBLCR_TAB_SIZE (1 << IP_VS_LBLCR_TAB_BITS) 81#define IP_VS_LBLCR_TAB_MASK (IP_VS_LBLCR_TAB_SIZE - 1) 82 83 84/* 85 * IPVS destination set structure and operations 86 */ 87struct ip_vs_dest_list { 88 struct ip_vs_dest_list *next; /* list link */ 89 struct ip_vs_dest *dest; /* destination server */ 90}; 91 92struct ip_vs_dest_set { 93 atomic_t size; /* set size */ 94 unsigned long lastmod; /* last modified time */ 95 struct ip_vs_dest_list *list; /* destination list */ 96 rwlock_t lock; /* lock for this list */ 97}; 98 99 100static struct ip_vs_dest_list * 101ip_vs_dest_set_insert(struct ip_vs_dest_set *set, struct ip_vs_dest *dest) 102{ 103 struct ip_vs_dest_list *e; 104 105 for (e=set->list; e!=NULL; e=e->next) { 106 if (e->dest == dest) 107 /* already existed */ 108 return NULL; 109 } 110 111 e = kmalloc(sizeof(struct ip_vs_dest_list), GFP_ATOMIC); 112 if (e == NULL) { 113 IP_VS_ERR("ip_vs_dest_set_insert(): no memory\n"); 114 return NULL; 115 } 116 117 atomic_inc(&dest->refcnt); 118 e->dest = dest; 119 120 /* link it to the list */ 121 write_lock(&set->lock); 122 e->next = set->list; 123 set->list = e; 124 atomic_inc(&set->size); 125 write_unlock(&set->lock); 126 127 set->lastmod = jiffies; 128 return e; 129} 130 131static void 132ip_vs_dest_set_erase(struct ip_vs_dest_set *set, struct ip_vs_dest *dest) 133{ 134 struct ip_vs_dest_list *e, **ep; 135 136 write_lock(&set->lock); 137 for (ep=&set->list, e=*ep; e!=NULL; e=*ep) { 138 if (e->dest == dest) { 139 /* HIT */ 140 *ep = e->next; 141 atomic_dec(&set->size); 142 set->lastmod = jiffies; 143 atomic_dec(&e->dest->refcnt); 144 kfree(e); 145 break; 146 } 147 ep = &e->next; 148 } 149 write_unlock(&set->lock); 150} 151 152static void ip_vs_dest_set_eraseall(struct ip_vs_dest_set *set) 153{ 154 struct ip_vs_dest_list *e, **ep; 155 156 write_lock(&set->lock); 157 for (ep=&set->list, e=*ep; e!=NULL; e=*ep) { 158 *ep = e->next; 159 /* 160 * We don't kfree dest because it is refered either 161 * by its service or by the trash dest list. 162 */ 163 atomic_dec(&e->dest->refcnt); 164 kfree(e); 165 } 166 write_unlock(&set->lock); 167} 168 169/* get weighted least-connection node in the destination set */ 170static inline struct ip_vs_dest *ip_vs_dest_set_min(struct ip_vs_dest_set *set) 171{ 172 register struct ip_vs_dest_list *e; 173 struct ip_vs_dest *dest, *least; 174 int loh, doh; 175 176 if (set == NULL) 177 return NULL; 178 179 read_lock(&set->lock); 180 /* select the first destination server, whose weight > 0 */ 181 for (e=set->list; e!=NULL; e=e->next) { 182 least = e->dest; 183 if (least->flags & IP_VS_DEST_F_OVERLOAD) 184 continue; 185 186 if ((atomic_read(&least->weight) > 0) 187 && (least->flags & IP_VS_DEST_F_AVAILABLE)) { 188 loh = atomic_read(&least->activeconns) * 50 189 + atomic_read(&least->inactconns); 190 goto nextstage; 191 } 192 } 193 read_unlock(&set->lock); 194 return NULL; 195 196 /* find the destination with the weighted least load */ 197 nextstage: 198 for (e=e->next; e!=NULL; e=e->next) { 199 dest = e->dest; 200 if (dest->flags & IP_VS_DEST_F_OVERLOAD) 201 continue; 202 203 doh = atomic_read(&dest->activeconns) * 50 204 + atomic_read(&dest->inactconns); 205 if ((loh * atomic_read(&dest->weight) > 206 doh * atomic_read(&least->weight)) 207 && (dest->flags & IP_VS_DEST_F_AVAILABLE)) { 208 least = dest; 209 loh = doh; 210 } 211 } 212 read_unlock(&set->lock); 213 214 IP_VS_DBG(6, "ip_vs_dest_set_min: server %d.%d.%d.%d:%d " 215 "activeconns %d refcnt %d weight %d overhead %d\n", 216 NIPQUAD(least->addr), ntohs(least->port), 217 atomic_read(&least->activeconns), 218 atomic_read(&least->refcnt), 219 atomic_read(&least->weight), loh); 220 return least; 221} 222 223 224/* get weighted most-connection node in the destination set */ 225static inline struct ip_vs_dest *ip_vs_dest_set_max(struct ip_vs_dest_set *set) 226{ 227 register struct ip_vs_dest_list *e; 228 struct ip_vs_dest *dest, *most; 229 int moh, doh; 230 231 if (set == NULL) 232 return NULL; 233 234 read_lock(&set->lock); 235 /* select the first destination server, whose weight > 0 */ 236 for (e=set->list; e!=NULL; e=e->next) { 237 most = e->dest; 238 if (atomic_read(&most->weight) > 0) { 239 moh = atomic_read(&most->activeconns) * 50 240 + atomic_read(&most->inactconns); 241 goto nextstage; 242 } 243 } 244 read_unlock(&set->lock); 245 return NULL; 246 247 /* find the destination with the weighted most load */ 248 nextstage: 249 for (e=e->next; e!=NULL; e=e->next) { 250 dest = e->dest; 251 doh = atomic_read(&dest->activeconns) * 50 252 + atomic_read(&dest->inactconns); 253 /* moh/mw < doh/dw ==> moh*dw < doh*mw, where mw,dw>0 */ 254 if ((moh * atomic_read(&dest->weight) < 255 doh * atomic_read(&most->weight)) 256 && (atomic_read(&dest->weight) > 0)) { 257 most = dest; 258 moh = doh; 259 } 260 } 261 read_unlock(&set->lock); 262 263 IP_VS_DBG(6, "ip_vs_dest_set_max: server %d.%d.%d.%d:%d " 264 "activeconns %d refcnt %d weight %d overhead %d\n", 265 NIPQUAD(most->addr), ntohs(most->port), 266 atomic_read(&most->activeconns), 267 atomic_read(&most->refcnt), 268 atomic_read(&most->weight), moh); 269 return most; 270} 271 272 273/* 274 * IPVS lblcr entry represents an association between destination 275 * IP address and its destination server set 276 */ 277struct ip_vs_lblcr_entry { 278 struct list_head list; 279 __be32 addr; /* destination IP address */ 280 struct ip_vs_dest_set set; /* destination server set */ 281 unsigned long lastuse; /* last used time */ 282}; 283 284 285/* 286 * IPVS lblcr hash table 287 */ 288struct ip_vs_lblcr_table { 289 rwlock_t lock; /* lock for this table */ 290 struct list_head bucket[IP_VS_LBLCR_TAB_SIZE]; /* hash bucket */ 291 atomic_t entries; /* number of entries */ 292 int max_size; /* maximum size of entries */ 293 struct timer_list periodic_timer; /* collect stale entries */ 294 int rover; /* rover for expire check */ 295 int counter; /* counter for no expire */ 296}; 297 298 299/* 300 * IPVS LBLCR sysctl table 301 */ 302 303static ctl_table vs_vars_table[] = { 304 { 305 .procname = "lblcr_expiration", 306 .data = &sysctl_ip_vs_lblcr_expiration, 307 .maxlen = sizeof(int), 308 .mode = 0644, 309 .proc_handler = &proc_dointvec_jiffies, 310 }, 311 { .ctl_name = 0 } 312}; 313 314static struct ctl_table_header * sysctl_header; 315 316/* 317 * new/free a ip_vs_lblcr_entry, which is a mapping of a destination 318 * IP address to a server. 319 */ 320static inline struct ip_vs_lblcr_entry *ip_vs_lblcr_new(__be32 daddr) 321{ 322 struct ip_vs_lblcr_entry *en; 323 324 en = kmalloc(sizeof(struct ip_vs_lblcr_entry), GFP_ATOMIC); 325 if (en == NULL) { 326 IP_VS_ERR("ip_vs_lblcr_new(): no memory\n"); 327 return NULL; 328 } 329 330 INIT_LIST_HEAD(&en->list); 331 en->addr = daddr; 332 333 /* initilize its dest set */ 334 atomic_set(&(en->set.size), 0); 335 en->set.list = NULL; 336 rwlock_init(&en->set.lock); 337 338 return en; 339} 340 341 342static inline void ip_vs_lblcr_free(struct ip_vs_lblcr_entry *en) 343{ 344 list_del(&en->list); 345 ip_vs_dest_set_eraseall(&en->set); 346 kfree(en); 347} 348 349 350/* 351 * Returns hash value for IPVS LBLCR entry 352 */ 353static inline unsigned ip_vs_lblcr_hashkey(__be32 addr) 354{ 355 return (ntohl(addr)*2654435761UL) & IP_VS_LBLCR_TAB_MASK; 356} 357 358 359/* 360 * Hash an entry in the ip_vs_lblcr_table. 361 * returns bool success. 362 */ 363static int 364ip_vs_lblcr_hash(struct ip_vs_lblcr_table *tbl, struct ip_vs_lblcr_entry *en) 365{ 366 unsigned hash; 367 368 if (!list_empty(&en->list)) { 369 IP_VS_ERR("ip_vs_lblcr_hash(): request for already hashed, " 370 "called from %p\n", __builtin_return_address(0)); 371 return 0; 372 } 373 374 /* 375 * Hash by destination IP address 376 */ 377 hash = ip_vs_lblcr_hashkey(en->addr); 378 379 write_lock(&tbl->lock); 380 list_add(&en->list, &tbl->bucket[hash]); 381 atomic_inc(&tbl->entries); 382 write_unlock(&tbl->lock); 383 384 return 1; 385} 386 387 388/* 389 * Get ip_vs_lblcr_entry associated with supplied parameters. 390 */ 391static inline struct ip_vs_lblcr_entry * 392ip_vs_lblcr_get(struct ip_vs_lblcr_table *tbl, __be32 addr) 393{ 394 unsigned hash; 395 struct ip_vs_lblcr_entry *en; 396 397 hash = ip_vs_lblcr_hashkey(addr); 398 399 read_lock(&tbl->lock); 400 401 list_for_each_entry(en, &tbl->bucket[hash], list) { 402 if (en->addr == addr) { 403 /* HIT */ 404 read_unlock(&tbl->lock); 405 return en; 406 } 407 } 408 409 read_unlock(&tbl->lock); 410 411 return NULL; 412} 413 414 415/* 416 * Flush all the entries of the specified table. 417 */ 418static void ip_vs_lblcr_flush(struct ip_vs_lblcr_table *tbl) 419{ 420 int i; 421 struct ip_vs_lblcr_entry *en, *nxt; 422 423 for (i=0; i<IP_VS_LBLCR_TAB_SIZE; i++) { 424 write_lock(&tbl->lock); 425 list_for_each_entry_safe(en, nxt, &tbl->bucket[i], list) { 426 ip_vs_lblcr_free(en); 427 atomic_dec(&tbl->entries); 428 } 429 write_unlock(&tbl->lock); 430 } 431} 432 433 434static inline void ip_vs_lblcr_full_check(struct ip_vs_lblcr_table *tbl) 435{ 436 unsigned long now = jiffies; 437 int i, j; 438 struct ip_vs_lblcr_entry *en, *nxt; 439 440 for (i=0, j=tbl->rover; i<IP_VS_LBLCR_TAB_SIZE; i++) { 441 j = (j + 1) & IP_VS_LBLCR_TAB_MASK; 442 443 write_lock(&tbl->lock); 444 list_for_each_entry_safe(en, nxt, &tbl->bucket[j], list) { 445 if (time_after(en->lastuse+sysctl_ip_vs_lblcr_expiration, 446 now)) 447 continue; 448 449 ip_vs_lblcr_free(en); 450 atomic_dec(&tbl->entries); 451 } 452 write_unlock(&tbl->lock); 453 } 454 tbl->rover = j; 455} 456 457 458/* 459 * Periodical timer handler for IPVS lblcr table 460 * It is used to collect stale entries when the number of entries 461 * exceeds the maximum size of the table. 462 * 463 * Fixme: we probably need more complicated algorithm to collect 464 * entries that have not been used for a long time even 465 * if the number of entries doesn't exceed the maximum size 466 * of the table. 467 * The full expiration check is for this purpose now. 468 */ 469static void ip_vs_lblcr_check_expire(unsigned long data) 470{ 471 struct ip_vs_lblcr_table *tbl; 472 unsigned long now = jiffies; 473 int goal; 474 int i, j; 475 struct ip_vs_lblcr_entry *en, *nxt; 476 477 tbl = (struct ip_vs_lblcr_table *)data; 478 479 if ((tbl->counter % COUNT_FOR_FULL_EXPIRATION) == 0) { 480 /* do full expiration check */ 481 ip_vs_lblcr_full_check(tbl); 482 tbl->counter = 1; 483 goto out; 484 } 485 486 if (atomic_read(&tbl->entries) <= tbl->max_size) { 487 tbl->counter++; 488 goto out; 489 } 490 491 goal = (atomic_read(&tbl->entries) - tbl->max_size)*4/3; 492 if (goal > tbl->max_size/2) 493 goal = tbl->max_size/2; 494 495 for (i=0, j=tbl->rover; i<IP_VS_LBLCR_TAB_SIZE; i++) { 496 j = (j + 1) & IP_VS_LBLCR_TAB_MASK; 497 498 write_lock(&tbl->lock); 499 list_for_each_entry_safe(en, nxt, &tbl->bucket[j], list) { 500 if (time_before(now, en->lastuse+ENTRY_TIMEOUT)) 501 continue; 502 503 ip_vs_lblcr_free(en); 504 atomic_dec(&tbl->entries); 505 goal--; 506 } 507 write_unlock(&tbl->lock); 508 if (goal <= 0) 509 break; 510 } 511 tbl->rover = j; 512 513 out: 514 mod_timer(&tbl->periodic_timer, jiffies+CHECK_EXPIRE_INTERVAL); 515} 516 517static int ip_vs_lblcr_init_svc(struct ip_vs_service *svc) 518{ 519 int i; 520 struct ip_vs_lblcr_table *tbl; 521 522 /* 523 * Allocate the ip_vs_lblcr_table for this service 524 */ 525 tbl = kmalloc(sizeof(struct ip_vs_lblcr_table), GFP_ATOMIC); 526 if (tbl == NULL) { 527 IP_VS_ERR("ip_vs_lblcr_init_svc(): no memory\n"); 528 return -ENOMEM; 529 } 530 svc->sched_data = tbl; 531 IP_VS_DBG(6, "LBLCR hash table (memory=%Zdbytes) allocated for " 532 "current service\n", 533 sizeof(struct ip_vs_lblcr_table)); 534 535 /* 536 * Initialize the hash buckets 537 */ 538 for (i=0; i<IP_VS_LBLCR_TAB_SIZE; i++) { 539 INIT_LIST_HEAD(&tbl->bucket[i]); 540 } 541 rwlock_init(&tbl->lock); 542 tbl->max_size = IP_VS_LBLCR_TAB_SIZE*16; 543 tbl->rover = 0; 544 tbl->counter = 1; 545 546 /* 547 * Hook periodic timer for garbage collection 548 */ 549 setup_timer(&tbl->periodic_timer, ip_vs_lblcr_check_expire, 550 (unsigned long)tbl); 551 tbl->periodic_timer.expires = jiffies+CHECK_EXPIRE_INTERVAL; 552 add_timer(&tbl->periodic_timer); 553 554 return 0; 555} 556 557 558static int ip_vs_lblcr_done_svc(struct ip_vs_service *svc) 559{ 560 struct ip_vs_lblcr_table *tbl = svc->sched_data; 561 562 /* remove periodic timer */ 563 del_timer_sync(&tbl->periodic_timer); 564 565 /* got to clean up table entries here */ 566 ip_vs_lblcr_flush(tbl); 567 568 /* release the table itself */ 569 kfree(svc->sched_data); 570 IP_VS_DBG(6, "LBLCR hash table (memory=%Zdbytes) released\n", 571 sizeof(struct ip_vs_lblcr_table)); 572 573 return 0; 574} 575 576 577static int ip_vs_lblcr_update_svc(struct ip_vs_service *svc) 578{ 579 return 0; 580} 581 582 583static inline struct ip_vs_dest * 584__ip_vs_wlc_schedule(struct ip_vs_service *svc, struct iphdr *iph) 585{ 586 struct ip_vs_dest *dest, *least; 587 int loh, doh; 588 589 /* 590 * We think the overhead of processing active connections is fifty 591 * times higher than that of inactive connections in average. (This 592 * fifty times might not be accurate, we will change it later.) We 593 * use the following formula to estimate the overhead: 594 * dest->activeconns*50 + dest->inactconns 595 * and the load: 596 * (dest overhead) / dest->weight 597 * 598 * Remember -- no floats in kernel mode!!! 599 * The comparison of h1*w2 > h2*w1 is equivalent to that of 600 * h1/w1 > h2/w2 601 * if every weight is larger than zero. 602 * 603 * The server with weight=0 is quiesced and will not receive any 604 * new connection. 605 */ 606 list_for_each_entry(dest, &svc->destinations, n_list) { 607 if (dest->flags & IP_VS_DEST_F_OVERLOAD) 608 continue; 609 610 if (atomic_read(&dest->weight) > 0) { 611 least = dest; 612 loh = atomic_read(&least->activeconns) * 50 613 + atomic_read(&least->inactconns); 614 goto nextstage; 615 } 616 } 617 return NULL; 618 619 /* 620 * Find the destination with the least load. 621 */ 622 nextstage: 623 list_for_each_entry_continue(dest, &svc->destinations, n_list) { 624 if (dest->flags & IP_VS_DEST_F_OVERLOAD) 625 continue; 626 627 doh = atomic_read(&dest->activeconns) * 50 628 + atomic_read(&dest->inactconns); 629 if (loh * atomic_read(&dest->weight) > 630 doh * atomic_read(&least->weight)) { 631 least = dest; 632 loh = doh; 633 } 634 } 635 636 IP_VS_DBG(6, "LBLCR: server %d.%d.%d.%d:%d " 637 "activeconns %d refcnt %d weight %d overhead %d\n", 638 NIPQUAD(least->addr), ntohs(least->port), 639 atomic_read(&least->activeconns), 640 atomic_read(&least->refcnt), 641 atomic_read(&least->weight), loh); 642 643 return least; 644} 645 646 647/* 648 * If this destination server is overloaded and there is a less loaded 649 * server, then return true. 650 */ 651static inline int 652is_overloaded(struct ip_vs_dest *dest, struct ip_vs_service *svc) 653{ 654 if (atomic_read(&dest->activeconns) > atomic_read(&dest->weight)) { 655 struct ip_vs_dest *d; 656 657 list_for_each_entry(d, &svc->destinations, n_list) { 658 if (atomic_read(&d->activeconns)*2 659 < atomic_read(&d->weight)) { 660 return 1; 661 } 662 } 663 } 664 return 0; 665} 666 667 668/* 669 * Locality-Based (weighted) Least-Connection scheduling 670 */ 671static struct ip_vs_dest * 672ip_vs_lblcr_schedule(struct ip_vs_service *svc, const struct sk_buff *skb) 673{ 674 struct ip_vs_dest *dest; 675 struct ip_vs_lblcr_table *tbl; 676 struct ip_vs_lblcr_entry *en; 677 struct iphdr *iph = ip_hdr(skb); 678 679 IP_VS_DBG(6, "ip_vs_lblcr_schedule(): Scheduling...\n"); 680 681 tbl = (struct ip_vs_lblcr_table *)svc->sched_data; 682 en = ip_vs_lblcr_get(tbl, iph->daddr); 683 if (en == NULL) { 684 dest = __ip_vs_wlc_schedule(svc, iph); 685 if (dest == NULL) { 686 IP_VS_DBG(1, "no destination available\n"); 687 return NULL; 688 } 689 en = ip_vs_lblcr_new(iph->daddr); 690 if (en == NULL) { 691 return NULL; 692 } 693 ip_vs_dest_set_insert(&en->set, dest); 694 ip_vs_lblcr_hash(tbl, en); 695 } else { 696 dest = ip_vs_dest_set_min(&en->set); 697 if (!dest || is_overloaded(dest, svc)) { 698 dest = __ip_vs_wlc_schedule(svc, iph); 699 if (dest == NULL) { 700 IP_VS_DBG(1, "no destination available\n"); 701 return NULL; 702 } 703 ip_vs_dest_set_insert(&en->set, dest); 704 } 705 if (atomic_read(&en->set.size) > 1 && 706 jiffies-en->set.lastmod > sysctl_ip_vs_lblcr_expiration) { 707 struct ip_vs_dest *m; 708 m = ip_vs_dest_set_max(&en->set); 709 if (m) 710 ip_vs_dest_set_erase(&en->set, m); 711 } 712 } 713 en->lastuse = jiffies; 714 715 IP_VS_DBG(6, "LBLCR: destination IP address %u.%u.%u.%u " 716 "--> server %u.%u.%u.%u:%d\n", 717 NIPQUAD(en->addr), 718 NIPQUAD(dest->addr), 719 ntohs(dest->port)); 720 721 return dest; 722} 723 724 725/* 726 * IPVS LBLCR Scheduler structure 727 */ 728static struct ip_vs_scheduler ip_vs_lblcr_scheduler = 729{ 730 .name = "lblcr", 731 .refcnt = ATOMIC_INIT(0), 732 .module = THIS_MODULE, 733 .init_service = ip_vs_lblcr_init_svc, 734 .done_service = ip_vs_lblcr_done_svc, 735 .update_service = ip_vs_lblcr_update_svc, 736 .schedule = ip_vs_lblcr_schedule, 737}; 738 739 740static int __init ip_vs_lblcr_init(void) 741{ 742 int ret; 743 744 INIT_LIST_HEAD(&ip_vs_lblcr_scheduler.n_list); 745 sysctl_header = register_sysctl_paths(net_vs_ctl_path, vs_vars_table); 746 ret = register_ip_vs_scheduler(&ip_vs_lblcr_scheduler); 747 if (ret) 748 unregister_sysctl_table(sysctl_header); 749 return ret; 750} 751 752 753static void __exit ip_vs_lblcr_cleanup(void) 754{ 755 unregister_sysctl_table(sysctl_header); 756 unregister_ip_vs_scheduler(&ip_vs_lblcr_scheduler); 757} 758 759 760module_init(ip_vs_lblcr_init); 761module_exit(ip_vs_lblcr_cleanup); 762MODULE_LICENSE("GPL");