Changeset View
Changeset View
Standalone View
Standalone View
head/sys/kern/uipc_usrreq.c
Show First 20 Lines • Show All 254 Lines • ▼ Show 20 Lines | |||||
* to perform namei() and other file system operations. | * to perform namei() and other file system operations. | ||||
*/ | */ | ||||
static struct rwlock unp_link_rwlock; | static struct rwlock unp_link_rwlock; | ||||
static struct mtx unp_defers_lock; | static struct mtx unp_defers_lock; | ||||
#define UNP_LINK_LOCK_INIT() rw_init(&unp_link_rwlock, \ | #define UNP_LINK_LOCK_INIT() rw_init(&unp_link_rwlock, \ | ||||
"unp_link_rwlock") | "unp_link_rwlock") | ||||
#define UNP_LINK_LOCK_ASSERT() rw_assert(&unp_link_rwlock, \ | #define UNP_LINK_LOCK_ASSERT() rw_assert(&unp_link_rwlock, \ | ||||
RA_LOCKED) | RA_LOCKED) | ||||
#define UNP_LINK_UNLOCK_ASSERT() rw_assert(&unp_link_rwlock, \ | #define UNP_LINK_UNLOCK_ASSERT() rw_assert(&unp_link_rwlock, \ | ||||
RA_UNLOCKED) | RA_UNLOCKED) | ||||
#define UNP_LINK_RLOCK() rw_rlock(&unp_link_rwlock) | #define UNP_LINK_RLOCK() rw_rlock(&unp_link_rwlock) | ||||
#define UNP_LINK_RUNLOCK() rw_runlock(&unp_link_rwlock) | #define UNP_LINK_RUNLOCK() rw_runlock(&unp_link_rwlock) | ||||
#define UNP_LINK_WLOCK() rw_wlock(&unp_link_rwlock) | #define UNP_LINK_WLOCK() rw_wlock(&unp_link_rwlock) | ||||
#define UNP_LINK_WUNLOCK() rw_wunlock(&unp_link_rwlock) | #define UNP_LINK_WUNLOCK() rw_wunlock(&unp_link_rwlock) | ||||
▲ Show 20 Lines • Show All 501 Lines • ▼ Show 20 Lines | uipc_detach(struct socket *so) | ||||
KASSERT(unp != NULL, ("uipc_detach: unp == NULL")); | KASSERT(unp != NULL, ("uipc_detach: unp == NULL")); | ||||
vp = NULL; | vp = NULL; | ||||
vplock = NULL; | vplock = NULL; | ||||
local_unp_rights = 0; | local_unp_rights = 0; | ||||
UNP_LINK_WLOCK(); | UNP_LINK_WLOCK(); | ||||
LIST_REMOVE(unp, unp_link); | LIST_REMOVE(unp, unp_link); | ||||
if (unp->unp_gcflag & UNPGC_DEAD) | |||||
LIST_REMOVE(unp, unp_dead); | |||||
unp->unp_gencnt = ++unp_gencnt; | unp->unp_gencnt = ++unp_gencnt; | ||||
--unp_count; | --unp_count; | ||||
UNP_LINK_WUNLOCK(); | UNP_LINK_WUNLOCK(); | ||||
UNP_PCB_UNLOCK_ASSERT(unp); | UNP_PCB_UNLOCK_ASSERT(unp); | ||||
restart: | restart: | ||||
if ((vp = unp->unp_vnode) != NULL) { | if ((vp = unp->unp_vnode) != NULL) { | ||||
vplock = mtx_pool_find(mtxpool_sleep, vp); | vplock = mtx_pool_find(mtxpool_sleep, vp); | ||||
▲ Show 20 Lines • Show All 1,687 Lines • ▼ Show 20 Lines | |||||
} | } | ||||
/* | /* | ||||
* unp_defer indicates whether additional work has been defered for a future | * unp_defer indicates whether additional work has been defered for a future | ||||
* pass through unp_gc(). It is thread local and does not require explicit | * pass through unp_gc(). It is thread local and does not require explicit | ||||
* synchronization. | * synchronization. | ||||
*/ | */ | ||||
static int unp_marked; | static int unp_marked; | ||||
static int unp_unreachable; | |||||
static void | static void | ||||
unp_accessable(struct filedescent **fdep, int fdcount) | unp_remove_dead_ref(struct filedescent **fdep, int fdcount) | ||||
{ | { | ||||
struct unpcb *unp; | struct unpcb *unp; | ||||
struct file *fp; | struct file *fp; | ||||
int i; | int i; | ||||
/* | |||||
* This function can only be called from the gc task. | |||||
*/ | |||||
KASSERT(taskqueue_member(taskqueue_thread, curthread) != 0, | |||||
("%s: not on gc callout", __func__)); | |||||
UNP_LINK_LOCK_ASSERT(); | |||||
for (i = 0; i < fdcount; i++) { | for (i = 0; i < fdcount; i++) { | ||||
fp = fdep[i]->fde_file; | fp = fdep[i]->fde_file; | ||||
if ((unp = fptounp(fp)) == NULL) | if ((unp = fptounp(fp)) == NULL) | ||||
continue; | continue; | ||||
if (unp->unp_gcflag & UNPGC_REF) | if ((unp->unp_gcflag & UNPGC_DEAD) == 0) | ||||
continue; | continue; | ||||
unp->unp_gcflag &= ~UNPGC_DEAD; | unp->unp_gcrefs--; | ||||
unp->unp_gcflag |= UNPGC_REF; | |||||
unp_marked++; | |||||
} | } | ||||
} | } | ||||
static void | static void | ||||
unp_gc_process(struct unpcb *unp) | unp_restore_undead_ref(struct filedescent **fdep, int fdcount) | ||||
{ | { | ||||
struct socket *so, *soa; | struct unpcb *unp; | ||||
struct file *fp; | struct file *fp; | ||||
int i; | |||||
/* Already processed. */ | |||||
if (unp->unp_gcflag & UNPGC_SCANNED) | |||||
return; | |||||
fp = unp->unp_file; | |||||
/* | /* | ||||
* Check for a socket potentially in a cycle. It must be in a | * This function can only be called from the gc task. | ||||
* queue as indicated by msgcount, and this must equal the file | |||||
* reference count. Note that when msgcount is 0 the file is NULL. | |||||
*/ | */ | ||||
if ((unp->unp_gcflag & UNPGC_REF) == 0 && fp && | KASSERT(taskqueue_member(taskqueue_thread, curthread) != 0, | ||||
unp->unp_msgcount != 0 && fp->f_count == unp->unp_msgcount) { | ("%s: not on gc callout", __func__)); | ||||
unp->unp_gcflag |= UNPGC_DEAD; | UNP_LINK_LOCK_ASSERT(); | ||||
unp_unreachable++; | |||||
return; | for (i = 0; i < fdcount; i++) { | ||||
fp = fdep[i]->fde_file; | |||||
if ((unp = fptounp(fp)) == NULL) | |||||
continue; | |||||
if ((unp->unp_gcflag & UNPGC_DEAD) == 0) | |||||
continue; | |||||
unp->unp_gcrefs++; | |||||
unp_marked++; | |||||
} | } | ||||
} | |||||
static void | |||||
unp_gc_scan(struct unpcb *unp, void (*op)(struct filedescent **, int)) | |||||
{ | |||||
struct socket *so, *soa; | |||||
so = unp->unp_socket; | so = unp->unp_socket; | ||||
SOCK_LOCK(so); | SOCK_LOCK(so); | ||||
if (SOLISTENING(so)) { | if (SOLISTENING(so)) { | ||||
/* | /* | ||||
* Mark all sockets in our accept queue. | * Mark all sockets in our accept queue. | ||||
*/ | */ | ||||
TAILQ_FOREACH(soa, &so->sol_comp, so_list) { | TAILQ_FOREACH(soa, &so->sol_comp, so_list) { | ||||
if (sotounpcb(soa)->unp_gcflag & UNPGC_IGNORE_RIGHTS) | if (sotounpcb(soa)->unp_gcflag & UNPGC_IGNORE_RIGHTS) | ||||
continue; | continue; | ||||
SOCKBUF_LOCK(&soa->so_rcv); | SOCKBUF_LOCK(&soa->so_rcv); | ||||
unp_scan(soa->so_rcv.sb_mb, unp_accessable); | unp_scan(soa->so_rcv.sb_mb, op); | ||||
SOCKBUF_UNLOCK(&soa->so_rcv); | SOCKBUF_UNLOCK(&soa->so_rcv); | ||||
} | } | ||||
} else { | } else { | ||||
/* | /* | ||||
* Mark all sockets we reference with RIGHTS. | * Mark all sockets we reference with RIGHTS. | ||||
*/ | */ | ||||
if ((unp->unp_gcflag & UNPGC_IGNORE_RIGHTS) == 0) { | if ((unp->unp_gcflag & UNPGC_IGNORE_RIGHTS) == 0) { | ||||
SOCKBUF_LOCK(&so->so_rcv); | SOCKBUF_LOCK(&so->so_rcv); | ||||
unp_scan(so->so_rcv.sb_mb, unp_accessable); | unp_scan(so->so_rcv.sb_mb, op); | ||||
SOCKBUF_UNLOCK(&so->so_rcv); | SOCKBUF_UNLOCK(&so->so_rcv); | ||||
} | } | ||||
} | } | ||||
SOCK_UNLOCK(so); | SOCK_UNLOCK(so); | ||||
unp->unp_gcflag |= UNPGC_SCANNED; | |||||
} | } | ||||
static int unp_recycled; | static int unp_recycled; | ||||
SYSCTL_INT(_net_local, OID_AUTO, recycled, CTLFLAG_RD, &unp_recycled, 0, | SYSCTL_INT(_net_local, OID_AUTO, recycled, CTLFLAG_RD, &unp_recycled, 0, | ||||
"Number of unreachable sockets claimed by the garbage collector."); | "Number of unreachable sockets claimed by the garbage collector."); | ||||
static int unp_taskcount; | static int unp_taskcount; | ||||
SYSCTL_INT(_net_local, OID_AUTO, taskcount, CTLFLAG_RD, &unp_taskcount, 0, | SYSCTL_INT(_net_local, OID_AUTO, taskcount, CTLFLAG_RD, &unp_taskcount, 0, | ||||
"Number of times the garbage collector has run."); | "Number of times the garbage collector has run."); | ||||
SYSCTL_UINT(_net_local, OID_AUTO, sockcount, CTLFLAG_RD, &unp_count, 0, | |||||
"Number of active local sockets."); | |||||
static void | static void | ||||
unp_gc(__unused void *arg, int pending) | unp_gc(__unused void *arg, int pending) | ||||
{ | { | ||||
struct unp_head *heads[] = { &unp_dhead, &unp_shead, &unp_sphead, | struct unp_head *heads[] = { &unp_dhead, &unp_shead, &unp_sphead, | ||||
NULL }; | NULL }; | ||||
struct unp_head **head; | struct unp_head **head; | ||||
struct unp_head unp_deadhead; /* List of potentially-dead sockets. */ | |||||
struct file *f, **unref; | struct file *f, **unref; | ||||
struct unpcb *unp; | struct unpcb *unp, *unptmp; | ||||
int i, total; | int i, total, unp_unreachable; | ||||
LIST_INIT(&unp_deadhead); | |||||
unp_taskcount++; | unp_taskcount++; | ||||
UNP_LINK_RLOCK(); | UNP_LINK_RLOCK(); | ||||
/* | /* | ||||
* First clear all gc flags from previous runs, apart from | * First determine which sockets may be in cycles. | ||||
* UNPGC_IGNORE_RIGHTS. | |||||
*/ | */ | ||||
unp_unreachable = 0; | |||||
for (head = heads; *head != NULL; head++) | for (head = heads; *head != NULL; head++) | ||||
LIST_FOREACH(unp, *head, unp_link) | LIST_FOREACH(unp, *head, unp_link) { | ||||
unp->unp_gcflag = | |||||
(unp->unp_gcflag & UNPGC_IGNORE_RIGHTS); | |||||
KASSERT((unp->unp_gcflag & ~UNPGC_IGNORE_RIGHTS) == 0, | |||||
("%s: unp %p has unexpected gc flags 0x%x", | |||||
__func__, unp, (unsigned int)unp->unp_gcflag)); | |||||
f = unp->unp_file; | |||||
/* | /* | ||||
* Scan marking all reachable sockets with UNPGC_REF. Once a socket | * Check for an unreachable socket potentially in a | ||||
* is reachable all of the sockets it references are reachable. | * cycle. It must be in a queue as indicated by | ||||
* msgcount, and this must equal the file reference | |||||
* count. Note that when msgcount is 0 the file is | |||||
* NULL. | |||||
*/ | |||||
if (f != NULL && unp->unp_msgcount != 0 && | |||||
f->f_count == unp->unp_msgcount) { | |||||
LIST_INSERT_HEAD(&unp_deadhead, unp, unp_dead); | |||||
unp->unp_gcflag |= UNPGC_DEAD; | |||||
unp->unp_gcrefs = unp->unp_msgcount; | |||||
unp_unreachable++; | |||||
} | |||||
} | |||||
/* | |||||
* Scan all sockets previously marked as potentially being in a cycle | |||||
* and remove the references each socket holds on any UNPGC_DEAD | |||||
* sockets in its queue. After this step, all remaining references on | |||||
* sockets marked UNPGC_DEAD should not be part of any cycle. | |||||
*/ | |||||
LIST_FOREACH(unp, &unp_deadhead, unp_dead) | |||||
unp_gc_scan(unp, unp_remove_dead_ref); | |||||
/* | |||||
* If a socket still has a non-negative refcount, it cannot be in a | |||||
* cycle. In this case increment refcount of all children iteratively. | |||||
* Stop the scan once we do a complete loop without discovering | * Stop the scan once we do a complete loop without discovering | ||||
* a new reachable socket. | * a new reachable socket. | ||||
*/ | */ | ||||
do { | do { | ||||
unp_unreachable = 0; | |||||
unp_marked = 0; | unp_marked = 0; | ||||
for (head = heads; *head != NULL; head++) | LIST_FOREACH_SAFE(unp, &unp_deadhead, unp_dead, unptmp) | ||||
LIST_FOREACH(unp, *head, unp_link) | if (unp->unp_gcrefs > 0) { | ||||
unp_gc_process(unp); | unp->unp_gcflag &= ~UNPGC_DEAD; | ||||
LIST_REMOVE(unp, unp_dead); | |||||
KASSERT(unp_unreachable > 0, | |||||
("%s: unp_unreachable underflow.", | |||||
__func__)); | |||||
unp_unreachable--; | |||||
unp_gc_scan(unp, unp_restore_undead_ref); | |||||
} | |||||
} while (unp_marked); | } while (unp_marked); | ||||
UNP_LINK_RUNLOCK(); | UNP_LINK_RUNLOCK(); | ||||
if (unp_unreachable == 0) | if (unp_unreachable == 0) | ||||
return; | return; | ||||
/* | /* | ||||
* Allocate space for a local list of dead unpcbs. | * Allocate space for a local array of dead unpcbs. | ||||
* TODO: can this path be simplified by instead using the local | |||||
* dead list at unp_deadhead, after taking out references | |||||
* on the file object and/or unpcb and dropping the link lock? | |||||
*/ | */ | ||||
unref = malloc(unp_unreachable * sizeof(struct file *), | unref = malloc(unp_unreachable * sizeof(struct file *), | ||||
M_TEMP, M_WAITOK); | M_TEMP, M_WAITOK); | ||||
/* | /* | ||||
* Iterate looking for sockets which have been specifically marked | * Iterate looking for sockets which have been specifically marked | ||||
* as as unreachable and store them locally. | * as unreachable and store them locally. | ||||
*/ | */ | ||||
UNP_LINK_RLOCK(); | UNP_LINK_RLOCK(); | ||||
for (total = 0, head = heads; *head != NULL; head++) | total = 0; | ||||
LIST_FOREACH(unp, *head, unp_link) | LIST_FOREACH(unp, &unp_deadhead, unp_dead) { | ||||
if ((unp->unp_gcflag & UNPGC_DEAD) != 0) { | KASSERT((unp->unp_gcflag & UNPGC_DEAD) != 0, | ||||
("%s: unp %p not marked UNPGC_DEAD", __func__, unp)); | |||||
unp->unp_gcflag &= ~UNPGC_DEAD; | |||||
f = unp->unp_file; | f = unp->unp_file; | ||||
if (unp->unp_msgcount == 0 || f == NULL || | if (unp->unp_msgcount == 0 || f == NULL || | ||||
f->f_count != unp->unp_msgcount || | f->f_count != unp->unp_msgcount || | ||||
!fhold(f)) | !fhold(f)) | ||||
continue; | continue; | ||||
unref[total++] = f; | unref[total++] = f; | ||||
KASSERT(total <= unp_unreachable, | KASSERT(total <= unp_unreachable, | ||||
("unp_gc: incorrect unreachable count.")); | ("%s: incorrect unreachable count.", __func__)); | ||||
} | } | ||||
UNP_LINK_RUNLOCK(); | UNP_LINK_RUNLOCK(); | ||||
/* | /* | ||||
* Now flush all sockets, free'ing rights. This will free the | * Now flush all sockets, free'ing rights. This will free the | ||||
* struct files associated with these sockets but leave each socket | * struct files associated with these sockets but leave each socket | ||||
* with one remaining ref. | * with one remaining ref. | ||||
*/ | */ | ||||
for (i = 0; i < total; i++) { | for (i = 0; i < total; i++) { | ||||
▲ Show 20 Lines • Show All 230 Lines • Show Last 20 Lines |