Add a new API for allocating unit number (-like) resources.
Allocation is always lowest free unit number. A mixed range/bitmap strategy for maximum memory efficiency. In the typical case where no unit numbers are freed total memory usage is 56 bytes on i386. malloc is called M_WAITOK but no locking is provided (yet). A bit of experience will be necessary to determine the best strategy. Hopefully a "caller provides locking" strategy can be maintained, but that may require use of M_NOWAIT allocation and failure handling. A userland test driver is included.
This commit is contained in:
parent
7327755fe8
commit
f6bde1fd05
@ -1143,6 +1143,7 @@ kern/subr_smp.c standard
|
||||
kern/subr_taskqueue.c standard
|
||||
kern/subr_trap.c standard
|
||||
kern/subr_turnstile.c standard
|
||||
kern/subr_unit.c standard
|
||||
kern/subr_witness.c optional witness
|
||||
kern/sys_generic.c standard
|
||||
kern/sys_pipe.c standard
|
||||
|
597
sys/kern/subr_unit.c
Normal file
597
sys/kern/subr_unit.c
Normal file
@ -0,0 +1,597 @@
|
||||
/*-
|
||||
* Copyright (c) 2004 Poul-Henning Kamp
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*
|
||||
* Unit number allocation functions.
|
||||
*
|
||||
* These functions implement a mixed run-length/bitmap management of unit
|
||||
* number spaces.
|
||||
*
|
||||
* Allocation is always lowest free number first.
|
||||
*
|
||||
* Worst case memory usage (disregarding boundary effects in the low end)
|
||||
* is two bits for each slot in the unit number space. (For a full
|
||||
* [0 ... UINT_MAX] space that is still a lot of course.)
|
||||
*
|
||||
* The typical case, where no unit numbers are freed, is managed in a
|
||||
* constant sized memory footprint of:
|
||||
* sizeof(struct unrhdr) + 2 * sizeof (struct unr) == 56 bytes on i386
|
||||
*
|
||||
* The caller must provide locking.
|
||||
*
|
||||
* A userland test program is included.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/queue.h>
|
||||
#include <sys/bitstring.h>
|
||||
|
||||
#ifdef _KERNEL
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/kernel.h>
|
||||
#include <sys/systm.h>
|
||||
|
||||
/*
|
||||
* In theory it would be smarter to allocate the individual blocks
|
||||
* with the zone allocator, but at this time the expectation is that
|
||||
* there will typically not even be enough allocations to fill a single
|
||||
* page, so we stick with malloc for now.
|
||||
*/
|
||||
static MALLOC_DEFINE(M_UNIT, "Unitno", "Unit number allocation");
|
||||
|
||||
#define Malloc(foo) malloc(foo, M_UNIT, M_WAITOK | M_ZERO)
|
||||
#define Free(foo) free(foo, M_UNIT)
|
||||
|
||||
#else /* ...USERLAND */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define KASSERT(cond, arg) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
printf arg; \
|
||||
exit (1); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define Malloc(foo) calloc(foo, 1)
|
||||
#define Free(foo) free(foo)
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* This is our basic building block.
|
||||
*
|
||||
* It can be used in three different ways depending on the value of the ptr
|
||||
* element:
|
||||
* If ptr is NULL, it represents a run of free items.
|
||||
* If ptr points to the unrhdr it represents a run of allocated items.
|
||||
* Otherwise it points to an bitstring of allocated items.
|
||||
*
|
||||
* For runs the len field is the length of the run.
|
||||
* For bitmaps the len field represents the number of allocated items.
|
||||
*
|
||||
* The bitmap is the same size as struct unr to optimize memory management.
|
||||
*/
|
||||
struct unr {
|
||||
TAILQ_ENTRY(unr) list;
|
||||
u_int len;
|
||||
void *ptr;
|
||||
};
|
||||
|
||||
/* Number of bits in the bitmap */
|
||||
#define NBITS (sizeof(struct unr) * 8)
|
||||
|
||||
/* Header element for a unr number space. */
|
||||
|
||||
struct unrhdr {
|
||||
TAILQ_HEAD(unrhd,unr) head;
|
||||
u_int low; /* Lowest item */
|
||||
u_int high; /* Highest item */
|
||||
u_int busy; /* Count of allocated items */
|
||||
u_int alloc; /* Count of memory allocations */
|
||||
};
|
||||
|
||||
|
||||
#if defined(DIAGNOSTIC) || !defined(_KERNEL)
|
||||
/*
|
||||
* Consistency check function.
|
||||
*
|
||||
* Checks the internal consistency as well as we can.
|
||||
*
|
||||
* Called at all boundaries of this API.
|
||||
*/
|
||||
static void
|
||||
check_unrhdr(struct unrhdr *uh, int line)
|
||||
{
|
||||
struct unr *up;
|
||||
u_int x, y, z, w;
|
||||
|
||||
y = 0;
|
||||
z = 0;
|
||||
TAILQ_FOREACH(up, &uh->head, list) {
|
||||
z++;
|
||||
if (up->ptr != uh && up->ptr != NULL) {
|
||||
z++;
|
||||
w = 0;
|
||||
for (x = 0; x < NBITS; x++)
|
||||
if (bit_test((bitstr_t *)up->ptr, x))
|
||||
w++;
|
||||
KASSERT (w == up->len,
|
||||
("UNR inconsistency: bits %u found %u\n",
|
||||
up->len, w));
|
||||
}
|
||||
if (up->ptr != NULL)
|
||||
y += up->len;
|
||||
}
|
||||
KASSERT (y == uh->busy,
|
||||
("UNR inconsistency: items %u found %u (line %d)\n",
|
||||
uh->busy, y, line));
|
||||
KASSERT (z == uh->alloc,
|
||||
("UNR inconsistency: chunks %u found %u (line %d)\n",
|
||||
uh->alloc, z, line));
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static __inline void
|
||||
check_unrhdr(struct unhdr *uh, int line)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Userland memory management. Just use calloc and keep track of how
|
||||
* many elements we have allocated for check_unrhdr().
|
||||
*/
|
||||
|
||||
static __inline void *
|
||||
new_unr(struct unrhdr *uh)
|
||||
{
|
||||
uh->alloc++;
|
||||
return (Malloc(sizeof (struct unr)));
|
||||
|
||||
}
|
||||
|
||||
static __inline void
|
||||
delete_unr(struct unrhdr *uh, void *ptr)
|
||||
{
|
||||
uh->alloc--;
|
||||
Free(ptr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate a new unrheader set.
|
||||
*
|
||||
* Highest and lowest valid values given as paramters.
|
||||
*/
|
||||
|
||||
struct unrhdr *
|
||||
new_unrhdr(u_int low, u_int high)
|
||||
{
|
||||
struct unrhdr *uh;
|
||||
struct unr *up;
|
||||
|
||||
KASSERT(low <= high,
|
||||
("UNR: use error: new_unrhdr(%u, %u)", low, high));
|
||||
uh = Malloc(sizeof *uh);
|
||||
TAILQ_INIT(&uh->head);
|
||||
uh->low = low;
|
||||
uh->high = high;
|
||||
up = new_unr(uh);
|
||||
up->len = 1 + (high - low);
|
||||
up->ptr = NULL;
|
||||
TAILQ_INSERT_HEAD(&uh->head, up, list);
|
||||
check_unrhdr(uh, __LINE__);
|
||||
return (uh);
|
||||
}
|
||||
|
||||
/*
|
||||
* See if a given unr should be collapsed with a neighbor
|
||||
*/
|
||||
static void
|
||||
collapse_unr(struct unrhdr *uh, struct unr *up)
|
||||
{
|
||||
struct unr *upp;
|
||||
|
||||
upp = TAILQ_PREV(up, unrhd, list);
|
||||
if (upp != NULL && up->ptr == upp->ptr) {
|
||||
up->len += upp->len;
|
||||
TAILQ_REMOVE(&uh->head, upp, list);
|
||||
delete_unr(uh, upp);
|
||||
}
|
||||
upp = TAILQ_NEXT(up, list);
|
||||
if (upp != NULL && up->ptr == upp->ptr) {
|
||||
up->len += upp->len;
|
||||
TAILQ_REMOVE(&uh->head, upp, list);
|
||||
delete_unr(uh, upp);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate a free unr.
|
||||
*/
|
||||
u_int
|
||||
alloc_unr(struct unrhdr *uh)
|
||||
{
|
||||
struct unr *up, *upp;
|
||||
u_int x;
|
||||
int y;
|
||||
|
||||
check_unrhdr(uh, __LINE__);
|
||||
x = uh->low;
|
||||
/*
|
||||
* We can always allocate from one of the first two unrs on the list.
|
||||
* The first one is likely an allocated run, but the second has to
|
||||
* be a free run or a bitmap.
|
||||
*/
|
||||
up = TAILQ_FIRST(&uh->head);
|
||||
KASSERT(up != NULL, ("UNR empty list"));
|
||||
if (up->ptr == uh) {
|
||||
x += up->len;
|
||||
up = TAILQ_NEXT(up, list);
|
||||
}
|
||||
KASSERT(up != NULL, ("UNR Ran out of numbers")); /* XXX */
|
||||
KASSERT(up->ptr != uh, ("UNR second element allocated"));
|
||||
|
||||
if (up->ptr != NULL) {
|
||||
/* Bitmap unr */
|
||||
KASSERT(up->len < NBITS, ("UNR bitmap confusion"));
|
||||
bit_ffc((bitstr_t *)up->ptr, NBITS, &y);
|
||||
KASSERT(y != -1, ("UNR corruption: No clear bit in bitmap."));
|
||||
bit_set((bitstr_t *)up->ptr, y);
|
||||
up->len++;
|
||||
uh->busy++;
|
||||
if (up->len == NBITS) {
|
||||
/* The unr is all allocated, drop bitmap */
|
||||
delete_unr(uh, up->ptr);
|
||||
up->ptr = uh;
|
||||
collapse_unr(uh, up);
|
||||
}
|
||||
check_unrhdr(uh, __LINE__);
|
||||
return (x + y);
|
||||
}
|
||||
|
||||
if (up->len == 1) {
|
||||
/* Run of one free item, grab it */
|
||||
up->ptr = uh;
|
||||
uh->busy++;
|
||||
collapse_unr(uh, up);
|
||||
check_unrhdr(uh, __LINE__);
|
||||
return (x);
|
||||
}
|
||||
|
||||
/*
|
||||
* Slice first item into an preceeding allocated run, even if we
|
||||
* have to create it. Because allocation is always lowest free
|
||||
* number first, we know the preceeding element (if any) to be
|
||||
* an allocated run.
|
||||
*/
|
||||
upp = TAILQ_PREV(up, unrhd, list);
|
||||
if (upp == NULL) {
|
||||
upp = new_unr(uh);
|
||||
upp->len = 0;
|
||||
upp->ptr = uh;
|
||||
TAILQ_INSERT_BEFORE(up, upp, list);
|
||||
}
|
||||
KASSERT(upp->ptr == uh, ("UNR list corruption"));
|
||||
upp->len++;
|
||||
up->len--;
|
||||
uh->busy++;
|
||||
check_unrhdr(uh, __LINE__);
|
||||
return (x);
|
||||
}
|
||||
|
||||
/*
|
||||
* Free a unr.
|
||||
*
|
||||
* If we can save unrs by using a bitmap, do so.
|
||||
*/
|
||||
void
|
||||
free_unr(struct unrhdr *uh, u_int item)
|
||||
{
|
||||
struct unr *up, *upp, *upn, *ul;
|
||||
u_int x, l, xl, n, pl;
|
||||
|
||||
KASSERT(item >= uh->low && item <= uh->high,
|
||||
("UNR: free_unr(%u) out of range [%u...%u]",
|
||||
item, uh->low, uh->high));
|
||||
check_unrhdr(uh, __LINE__);
|
||||
item -= uh->low;
|
||||
xl = x = 0;
|
||||
/* Find the start of the potential bitmap */
|
||||
l = item - item % NBITS;
|
||||
ul = 0;
|
||||
TAILQ_FOREACH(up, &uh->head, list) {
|
||||
|
||||
/* Keep track of which unr we'll split if we do */
|
||||
if (x <= l) {
|
||||
ul = up;
|
||||
xl = x;
|
||||
}
|
||||
|
||||
/* Handle bitmap items */
|
||||
if (up->ptr != NULL && up->ptr != uh) {
|
||||
if (x + NBITS <= item) { /* not yet */
|
||||
x += NBITS;
|
||||
continue;
|
||||
}
|
||||
KASSERT(bit_test((bitstr_t *)up->ptr, item - x) != 0,
|
||||
("UNR: Freeing free item %d (%d) (bitmap)\n",
|
||||
item, item - x));
|
||||
bit_clear((bitstr_t *)up->ptr, item - x);
|
||||
uh->busy--;
|
||||
up->len--;
|
||||
/*
|
||||
* XXX: up->len == 1 could possibly be collapsed to
|
||||
* XXX: neighboring runs.
|
||||
*/
|
||||
if (up->len > 0)
|
||||
return;
|
||||
/* We have freed all items in bitmap, drop it */
|
||||
delete_unr(uh, up->ptr);
|
||||
up->ptr = NULL;
|
||||
up->len = NBITS;
|
||||
collapse_unr(uh, up);
|
||||
check_unrhdr(uh, __LINE__);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Run length unr's */
|
||||
|
||||
if (x + up->len <= item) { /* not yet */
|
||||
x += up->len;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* We now have our run length unr */
|
||||
KASSERT(up->ptr == uh,
|
||||
("UNR Freeing free item %d (run))\n", item));
|
||||
|
||||
/* Just this one left, reap it */
|
||||
if (up->len == 1) {
|
||||
up->ptr = NULL;
|
||||
uh->busy--;
|
||||
collapse_unr(uh, up);
|
||||
check_unrhdr(uh, __LINE__);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check if we can shift the item to the previous run */
|
||||
upp = TAILQ_PREV(up, unrhd, list);
|
||||
if (item == x && upp != NULL && upp->ptr == NULL) {
|
||||
upp->len++;
|
||||
up->len--;
|
||||
uh->busy--;
|
||||
check_unrhdr(uh, __LINE__);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check if we can shift the item to the next run */
|
||||
upn = TAILQ_NEXT(up, list);
|
||||
if (item == x + up->len - 1 &&
|
||||
upn != NULL && upn->ptr == NULL) {
|
||||
upn->len++;
|
||||
up->len--;
|
||||
uh->busy--;
|
||||
check_unrhdr(uh, __LINE__);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Split off the tail end, if any. */
|
||||
pl = up->len - (1 + (item - x));
|
||||
if (pl > 0) {
|
||||
upp = new_unr(uh);
|
||||
upp->ptr = uh;
|
||||
upp->len = pl;
|
||||
TAILQ_INSERT_AFTER(&uh->head, up, upp, list);
|
||||
}
|
||||
|
||||
if (item == x) {
|
||||
/* We are done splitting */
|
||||
up->len = 1;
|
||||
up->ptr = NULL;
|
||||
} else {
|
||||
/* The freed item */
|
||||
upp = new_unr(uh);
|
||||
upp->len = 1;
|
||||
upp->ptr = NULL;
|
||||
TAILQ_INSERT_AFTER(&uh->head, up, upp, list);
|
||||
/* Adjust current unr */
|
||||
up->len = item - x;
|
||||
}
|
||||
|
||||
uh->busy--;
|
||||
check_unrhdr(uh, __LINE__);
|
||||
|
||||
/* Our ul marker element may have shifted one later */
|
||||
if (ul->len + xl <= l) {
|
||||
xl += ul->len;
|
||||
ul = TAILQ_NEXT(ul, list);
|
||||
}
|
||||
KASSERT(ul != NULL, ("UNR lost bitmap pointer"));
|
||||
|
||||
/* Count unrs entirely inside potential bitmap */
|
||||
n = 0;
|
||||
pl = xl;
|
||||
item = l + NBITS;
|
||||
for (up = ul;
|
||||
up != NULL && pl + up->len <= item;
|
||||
up = TAILQ_NEXT(up, list)) {
|
||||
if (pl >= l)
|
||||
n++;
|
||||
pl += up->len;
|
||||
}
|
||||
|
||||
/* If less than three, a bitmap does not pay off */
|
||||
if (n < 3)
|
||||
return;
|
||||
|
||||
/* Allocate bitmap */
|
||||
upp = new_unr(uh);
|
||||
upp->ptr = new_unr(uh);
|
||||
|
||||
/* Insert bitmap after ul element */
|
||||
TAILQ_INSERT_AFTER(&uh->head, ul, upp, list);
|
||||
|
||||
/* Slice off the tail from the ul element */
|
||||
pl = ul->len - (l - xl);
|
||||
if (ul->ptr != NULL) {
|
||||
bit_nset(upp->ptr, 0, pl - 1);
|
||||
upp->len = pl;
|
||||
}
|
||||
ul->len -= pl;
|
||||
|
||||
/* Ditch ul if it got reduced to zero size */
|
||||
if (ul->len == 0) {
|
||||
TAILQ_REMOVE(&uh->head, ul, list);
|
||||
delete_unr(uh, ul);
|
||||
}
|
||||
|
||||
/* Soak up run length unrs until we have absorbed NBITS */
|
||||
while (pl != NBITS) {
|
||||
|
||||
/* Grab first one in line */
|
||||
upn = TAILQ_NEXT(upp, list);
|
||||
|
||||
/* We may not have a multiple of NBITS totally */
|
||||
if (upn == NULL)
|
||||
break;
|
||||
|
||||
/* Run may extend past our new bitmap */
|
||||
n = NBITS - pl;
|
||||
if (n > upn->len)
|
||||
n = upn->len;
|
||||
|
||||
if (upn->ptr != NULL) {
|
||||
bit_nset(upp->ptr, pl, pl + n - 1);
|
||||
upp->len += n;
|
||||
}
|
||||
pl += n;
|
||||
|
||||
if (n != upn->len) {
|
||||
/* We did not absorb the entire run */
|
||||
upn->len -= n;
|
||||
break;
|
||||
}
|
||||
TAILQ_REMOVE(&uh->head, upn, list);
|
||||
delete_unr(uh, upn);
|
||||
}
|
||||
check_unrhdr(uh, __LINE__);
|
||||
return;
|
||||
}
|
||||
KASSERT(0 != 1, ("UNR: Fell off the end in free_unr()"));
|
||||
}
|
||||
|
||||
#ifndef _KERNEL /* USERLAND test driver */
|
||||
|
||||
/*
|
||||
* Simple stochastic test driver for the above functions
|
||||
*/
|
||||
|
||||
static void
|
||||
print_unr(struct unrhdr *uh, struct unr *up)
|
||||
{
|
||||
u_int x;
|
||||
|
||||
printf(" %p len = %5u ", up, up->len);
|
||||
if (up->ptr == NULL)
|
||||
printf("free\n");
|
||||
else if (up->ptr == uh)
|
||||
printf("alloc\n");
|
||||
else {
|
||||
printf(" [");
|
||||
for (x = 0; x < NBITS; x++) {
|
||||
if (bit_test((bitstr_t *)up->ptr, x))
|
||||
putchar('#');
|
||||
else
|
||||
putchar(' ');
|
||||
}
|
||||
printf("]\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
print_unrhdr(struct unrhdr *uh)
|
||||
{
|
||||
struct unr *up;
|
||||
u_int x;
|
||||
|
||||
printf("%p low = %u high = %u busy %u\n",
|
||||
uh, uh->low, uh->high, uh->busy);
|
||||
x = uh->low;
|
||||
TAILQ_FOREACH(up, &uh->head, list) {
|
||||
printf(" from = %5u", x);
|
||||
print_unr(uh, up);
|
||||
if (up->ptr == NULL || up->ptr == uh)
|
||||
x += up->len;
|
||||
else
|
||||
x += NBITS;
|
||||
}
|
||||
}
|
||||
|
||||
/* Number of unrs to test */
|
||||
#define NN 10000
|
||||
|
||||
int
|
||||
main(int argc __unused, const char **argv __unused)
|
||||
{
|
||||
struct unrhdr *uh;
|
||||
int i, x;
|
||||
char a[NN];
|
||||
|
||||
uh = new_unrhdr(0, NN - 1);
|
||||
|
||||
memset(a, 0, sizeof a);
|
||||
|
||||
fprintf(stderr, "sizeof(struct unr) %d\n", sizeof (struct unr));
|
||||
fprintf(stderr, "sizeof(struct unrhdr) %d\n", sizeof (struct unrhdr));
|
||||
x = 1;
|
||||
for (;;) {
|
||||
i = random() % NN;
|
||||
if (a[i]) {
|
||||
printf("F %u\n", i);
|
||||
free_unr(uh, i);
|
||||
a[i] = 0;
|
||||
} else {
|
||||
i = alloc_unr(uh);
|
||||
a[i] = 1;
|
||||
printf("A %u\n", i);
|
||||
}
|
||||
if (1) /* XXX: change this for detailed debug printout */
|
||||
print_unrhdr(uh);
|
||||
check_unrhdr(uh, __LINE__);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
#endif
|
@ -306,4 +306,12 @@ int umajor(dev_t dev);
|
||||
/* XXX: Should be void nanodelay(u_int nsec); */
|
||||
void DELAY(int usec);
|
||||
|
||||
/*
|
||||
* Unit number allocation API. (kern/subr_unit.c)
|
||||
*/
|
||||
struct unrhdr;
|
||||
struct unrhdr *new_unrhdr(u_int low, u_int high);
|
||||
u_int alloc_unr(struct unrhdr *uh);
|
||||
void free_unr(struct unrhdr *uh, u_int item);
|
||||
|
||||
#endif /* !_SYS_SYSTM_H_ */
|
||||
|
Loading…
x
Reference in New Issue
Block a user