Subject: Re: high input packet rate can lead to process starvation
To: None <tad@entrisphere.com>
From: YAMAMOTO Takashi <yamt@mwd.biglobe.ne.jp>
List: tech-net
Date: 09/23/2006 15:54:21
--NextPart-20060923152343-0172400
Content-Type: Text/Plain; charset=us-ascii
> As long as there are ip packets in the queue, ipintr() will continue
> happily processing them.
>
> This is really a problem not just with the ipintrq, but with all of the
> software-interrupt protocol input routines.
>
> My solution (this is a hack) is to put a time-limit on how long ipintr()
> is allowed to run. If it runs longer than this without emptying the
> queue, I set a global "ipdisabled" flag and start a callout ticking that
> will wakeup, clear the flag, and setsoftnet() (code attached). In
> addition the ethernet driver drops all packets as long as ipdisabled is
> true. (See the attachment for code)
it reminds me the attached code.
(i thought i've filed a PR but couldn't find a number.)
the idea was let a thread process the queue when it's somewhat busy.
YAMAMOTO Takashi
--NextPart-20060923152343-0172400
Content-Type: Text/Plain; charset=us-ascii
Content-Disposition: attachment; filename="netisr.c"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/kthread.h>
#include <sys/lock.h>
#include <sys/proc.h>
#include <net/if.h>
#include <net/netisr.h>
#if 1
/* XXX should be per netisr */
#define NETISR_COUNTER_DEFINE(name) \
struct evcnt netisr_stat_##name \
= EVCNT_INITIALIZER(EVCNT_TYPE_MISC, NULL, "netisr", #name); \
EVCNT_ATTACH_STATIC(netisr_stat_##name)
#define NETISR_COUNTER_INCL(name) (netisr_stat_##name).ev_count++
#else
#define NETISR_COUNTER_DEFINE(name) /* nothing */
#define NETISR_COUNTER_INCL(name) /* nothing */
#endif
NETISR_COUNTER_DEFINE(sched);
NETISR_COUNTER_DEFINE(thread);
NETISR_COUNTER_DEFINE(intr);
NETISR_COUNTER_DEFINE(transit);
NETISR_COUNTER_DEFINE(yield);
NETISR_COUNTER_DEFINE(sleep);
NETISR_COUNTER_DEFINE(throttle);
static void netisr_create_thread(void *);
static void netisr_thread(void *);
static __inline void netisr_schedule(struct netisr *);
static __inline void netisr_wakeup_thread(struct netisr *);
static __inline boolean_t netisr_empty(const struct netisr *) __unused;
static __inline struct mbuf *netisr_dequeue(struct netisr *);
int netisr_dothrottle = 1;
int netisr_maxrun = 10240;
static __inline boolean_t
netisr_empty(const struct netisr *ni)
{
return ni->ni_queue.ifq_len == 0;
}
static __inline void
netisr_wakeup_thread(struct netisr *ni)
{
if (ni->ni_threadsleeping) {
wakeup(ni);
}
}
static __inline void
netisr_schedule(struct netisr *ni)
{
NETISR_COUNTER_INCL(sched);
if (__predict_false(ni->ni_usethread)) {
netisr_wakeup_thread(ni);
} else {
schednetisr(ni->ni_isr);
}
}
/*
* netisr_enqueue:
*
* => called at splnet.
*/
int
netisr_enqueue(struct netisr *ni, struct mbuf *m)
{
struct ifqueue *q = &ni->ni_queue;
simple_lock(&ni->ni_lock);
if (IF_QFULL(q) || ni->ni_throttle) {
IF_DROP(q);
if (!ni->ni_throttle && netisr_dothrottle) {
ni->ni_throttle = TRUE;
NETISR_COUNTER_INCL(throttle);
}
simple_unlock(&ni->ni_lock);
m_freem(m);
return ENOBUFS;
}
if (netisr_empty(ni)) {
netisr_schedule(ni);
}
IF_ENQUEUE(q, m);
simple_unlock(&ni->ni_lock);
return 0;
}
/*
* netisr_dequeue:
*
* => called with ni_lock locked.
*/
static __inline struct mbuf *
netisr_dequeue(struct netisr *ni)
{
struct mbuf *m;
IF_DEQUEUE(&ni->ni_queue, m);
if (m == NULL) {
ni->ni_throttle = FALSE;
}
return m;
}
void
netisr_init(struct netisr *ni, const char *name, int isr,
void (*handler)(struct mbuf *), int maxqlen)
{
struct ifqueue *q;
q = &ni->ni_queue;
memset(q, 0, sizeof(*q));
q->ifq_maxlen = maxqlen;
ni->ni_isr = isr;
ni->ni_name = name;
ni->ni_handler = handler;
ni->ni_maxrun = 64; /* XXX */
ni->ni_minrun = 16; /* XXX */
ni->ni_usethread = FALSE;
simple_lock_init(&ni->ni_lock);
kthread_create(netisr_create_thread, ni);
}
static void
netisr_create_thread(void *arg)
{
struct netisr *ni = arg;
int error;
error = kthread_create1(netisr_thread, ni, &ni->ni_proc, ni->ni_name);
if (error) {
panic("netisr_create_thread: %s: %d", ni->ni_name, error);
}
}
/*
* netisr_run:
*
* => called at splsoftnet.
*/
void
netisr_run(struct netisr *ni)
{
// int rest = ni->ni_maxrun;
int rest = netisr_maxrun;
int s;
while ((rest--) > 0) {
struct mbuf *m;
s = splnet();
simple_lock(&ni->ni_lock);
m = netisr_dequeue(ni);
simple_unlock(&ni->ni_lock);
splx(s);
if (m == NULL) {
return;
}
NETISR_COUNTER_INCL(intr);
ni->ni_handler(m);
}
NETISR_COUNTER_INCL(transit);
s = splnet();
simple_lock(&ni->ni_lock);
ni->ni_usethread = TRUE;
netisr_wakeup_thread(ni);
simple_unlock(&ni->ni_lock);
splx(s);
}
static void
netisr_thread(void *arg)
{
struct netisr *ni = arg;
int s;
s = splnet();
simple_lock(&ni->ni_lock);
again:
while (!ni->ni_usethread) {
ni->ni_threadsleeping = TRUE;
ltsleep(ni, PUSER, ni->ni_name, 0, &ni->ni_lock);
}
ni->ni_threadsleeping = FALSE;
simple_unlock(&ni->ni_lock);
splx(s);
while (/* CONSTCOND */ 1) {
struct mbuf *m;
s = splnet();
simple_lock(&ni->ni_lock);
m = netisr_dequeue(ni);
if (m == NULL) {
break;
}
simple_unlock(&ni->ni_lock);
splx(s);
NETISR_COUNTER_INCL(thread);
s = splsoftnet();
ni->ni_handler(m);
splx(s);
if ((curcpu()->ci_schedstate.spc_flags & SPCF_SHOULDYIELD)) {
NETISR_COUNTER_INCL(yield);
preempt(1);
}
}
NETISR_COUNTER_INCL(yield);
ni->ni_usethread = FALSE;
ni->ni_throttle = FALSE;
goto again;
}
--NextPart-20060923152343-0172400--