From c9e9fb21c9ab82a8102647d67bcf956e0aba60af Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Sat, 28 Aug 2010 17:18:34 -0700 Subject: [PATCH] kernel - All lwkt thread now start out mpsafe part 1/2 * All callers of lwkt_init_thread(), lwkt_create() and lwkt_alloc_thread() now always pass TDF_MPSAFE and the flag is asserted in the low level thread creation code. --- sys/dev/netif/iwl/if_iwl.c | 6 ++-- sys/kern/init_main.c | 6 +++- sys/kern/kern_intr.c | 70 ++++---------------------------------- sys/kern/kern_nrandom.c | 10 ++++-- sys/kern/kern_timeout.c | 12 +++---- sys/kern/lwkt_thread.c | 8 +++-- sys/kern/subr_bus.c | 8 +++-- sys/kern/subr_disk.c | 11 ++++-- sys/kern/usched_bsd4.c | 19 ++++++----- sys/kern/usched_dummy.c | 9 ++--- sys/kern/vfs_journal.c | 15 ++++++-- sys/net/route.c | 5 ++- sys/vfs/devfs/devfs_core.c | 26 +++++++++----- sys/vfs/nfs/nfs_iod.c | 13 ++++++- sys/vfs/nfs/nfs_vfsops.c | 4 +-- 15 files changed, 109 insertions(+), 113 deletions(-) diff --git a/sys/dev/netif/iwl/if_iwl.c b/sys/dev/netif/iwl/if_iwl.c index 7049c7f336..e38bd3cdb9 100644 --- a/sys/dev/netif/iwl/if_iwl.c +++ b/sys/dev/netif/iwl/if_iwl.c @@ -479,6 +479,7 @@ iwl_service_loop(void *arg) struct ifnet *ifp = &iwl->iwl_ic.ic_if; struct netmsg *nmsg; + get_mplock(); lwkt_serialize_enter(ifp->if_serializer); while ((nmsg = lwkt_waitport(&iwl->iwl_thread_port, 0))) { nmsg->nm_dispatch(nmsg); @@ -486,8 +487,7 @@ iwl_service_loop(void *arg) break; } lwkt_serialize_exit(ifp->if_serializer); - - lwkt_exit(); + rel_mplock(); } void @@ -503,7 +503,7 @@ iwl_create_thread(struct iwlcom *iwl, int unit) iwl->iwl_thread_port.mp_putport = iwl_put_port; lwkt_create(iwl_service_loop, iwl, NULL, &iwl->iwl_thread, - 0, unit % ncpus, "iwl%d", unit); + TDF_MPSAFE, unit % ncpus, "iwl%d", unit); } static void diff --git a/sys/kern/init_main.c b/sys/kern/init_main.c index fe22b97f70..d56243690f 100644 --- a/sys/kern/init_main.c +++ b/sys/kern/init_main.c @@ -161,8 +161,12 @@ sysinit_add(struct sysinit **set, struct sysinit **set_end) void mi_proc0init(struct globaldata *gd, struct user *proc0paddr) { - lwkt_init_thread(&thread0, proc0paddr, LWKT_THREAD_STACK, 0, gd); + lwkt_init_thread(&thread0, proc0paddr, LWKT_THREAD_STACK, + TDF_MPSAFE, gd); lwkt_set_comm(&thread0, "thread0"); +#ifdef SMP + thread0.td_mpcount = 1; /* will hold mplock initially */ +#endif RB_INIT(&proc0.p_lwp_tree); spin_init(&proc0.p_spin); proc0.p_lasttid = 0; /* +1 = next TID */ diff --git a/sys/kern/kern_intr.c b/sys/kern/kern_intr.c index c0a46dde97..f4efa9d025 100644 --- a/sys/kern/kern_intr.c +++ b/sys/kern/kern_intr.c @@ -88,9 +88,6 @@ static void ithread_emergency(void *arg); static void report_stray_interrupt(int intr, struct intr_info *info); static void int_moveto_destcpu(int *, int *, int); static void int_moveto_origcpu(int, int); -#ifdef SMP -static void intr_get_mplock(void); -#endif int intr_info_size = sizeof(intr_info_ary) / sizeof(intr_info_ary[0]); @@ -101,18 +98,6 @@ static struct thread emergency_intr_thread; #define ISTATE_NORMAL 1 #define ISTATE_LIVELOCKED 2 -#ifdef SMP -static int intr_mpsafe = 1; -static int intr_migrate = 0; -static int intr_migrate_count; -TUNABLE_INT("kern.intr_mpsafe", &intr_mpsafe); -SYSCTL_INT(_kern, OID_AUTO, intr_mpsafe, - CTLFLAG_RW, &intr_mpsafe, 0, "Run INTR_MPSAFE handlers without the BGL"); -SYSCTL_INT(_kern, OID_AUTO, intr_migrate, - CTLFLAG_RW, &intr_migrate, 0, "Migrate to cpu holding BGL"); -SYSCTL_INT(_kern, OID_AUTO, intr_migrate_count, - CTLFLAG_RW, &intr_migrate_count, 0, ""); -#endif static int livelock_limit = 40000; static int livelock_lowater = 20000; static int livelock_debug = -1; @@ -236,8 +221,9 @@ register_int(int intr, inthand2_t *handler, void *arg, const char *name, */ if (emergency_intr_thread.td_kstack == NULL) { lwkt_create(ithread_emergency, NULL, NULL, - &emergency_intr_thread, TDF_STOPREQ|TDF_INTTHREAD, -1, - "ithread emerg"); + &emergency_intr_thread, + TDF_STOPREQ|TDF_INTTHREAD|TDF_MPSAFE, + -1, "ithread emerg"); systimer_init_periodic_nq(&emergency_intr_timer, emergency_intr_timer_callback, &emergency_intr_thread, (emergency_intr_enable ? emergency_intr_freq : 1)); @@ -769,15 +755,10 @@ ithread_handler(void *arg) * always operate with the BGL. */ #ifdef SMP - if (intr_mpsafe == 0) { - if (mpheld == 0) { - intr_get_mplock(); - mpheld = 1; - } - } else if (info->i_mplock_required != mpheld) { + if (info->i_mplock_required != mpheld) { if (info->i_mplock_required) { KKASSERT(mpheld == 0); - intr_get_mplock(); + get_mplock(); mpheld = 1; } else { KKASSERT(mpheld != 0); @@ -785,11 +766,6 @@ ithread_handler(void *arg) mpheld = 0; } } - - /* - * scheduled cpu may have changed, see intr_get_mplock() - */ - gd = mycpu; #endif /* @@ -866,12 +842,6 @@ ithread_handler(void *arg) */ if (ill_count <= livelock_limit) { if (info->i_running == 0) { -#ifdef SMP - if (mpheld && intr_migrate) { - rel_mplock(); - mpheld = 0; - } -#endif lwkt_deschedule_self(gd->gd_curthread); lwkt_switch(); } @@ -925,34 +895,6 @@ ithread_handler(void *arg) /* not reached */ } -#ifdef SMP - -/* - * An interrupt thread is trying to get the MP lock. To avoid cpu-bound - * code in the kernel on cpu X from interfering we chase the MP lock. - */ -static void -intr_get_mplock(void) -{ - int owner; - - if (intr_migrate == 0) { - get_mplock(); - return; - } - while (try_mplock() == 0) { - owner = owner_mplock(); - if (owner >= 0 && owner != mycpu->gd_cpuid) { - lwkt_migratecpu(owner); - ++intr_migrate_count; - } else { - lwkt_switch(); - } - } -} - -#endif - /* * Emergency interrupt polling thread. The thread begins execution * outside a critical section with the BGL held. @@ -975,6 +917,8 @@ ithread_emergency(void *arg __unused) intrec_t rec, nrec; int intr; + get_mplock(); + for (;;) { for (intr = 0; intr < max_installed_hard_intr; ++intr) { info = &intr_info_ary[intr]; diff --git a/sys/kern/kern_nrandom.c b/sys/kern/kern_nrandom.c index 5ad67d6035..2a140bb4aa 100644 --- a/sys/kern/kern_nrandom.c +++ b/sys/kern/kern_nrandom.c @@ -140,6 +140,7 @@ #include #include +#include /* * Portability note: The u_char/unsigned char type is used where @@ -568,6 +569,8 @@ read_random_unlimited(void *buf, u_int nbytes) /* * Random number generator helper thread. This limits code overhead from * high frequency events by delaying the clearing of rand_thread_signal. + * + * MPSAFE thread */ static void @@ -575,10 +578,12 @@ rand_thread_loop(void *dummy) { int count; + get_mplock(); + for (;;) { NANOUP_EVENT (); spin_lock_wr(&rand_spin); - count = (int)(L15_Byte() * hz / (256 * 10) + hz / 10); + count = (int)(L15_Byte() * hz / (256 * 10) + hz / 10 + 1); spin_unlock_wr(&rand_spin); tsleep(rand_td, 0, "rwait", count); crit_enter(); @@ -594,7 +599,8 @@ static void rand_thread_init(void) { - lwkt_create(rand_thread_loop, NULL, &rand_td, NULL, 0, 0, "random"); + lwkt_create(rand_thread_loop, NULL, &rand_td, NULL, + TDF_MPSAFE, 0, "random"); } SYSINIT(rand, SI_SUB_HELPER_THREADS, SI_ORDER_ANY, rand_thread_init, 0); diff --git a/sys/kern/kern_timeout.c b/sys/kern/kern_timeout.c index 1639197865..f27f209152 100644 --- a/sys/kern/kern_timeout.c +++ b/sys/kern/kern_timeout.c @@ -177,8 +177,8 @@ swi_softclock_setup(void *arg) * the cpu they were scheduled on. */ lwkt_create(softclock_handler, sc, NULL, - &sc->thread, TDF_STOPREQ|TDF_INTTHREAD, cpu, - "softclock %d", cpu); + &sc->thread, TDF_STOPREQ|TDF_INTTHREAD|TDF_MPSAFE, + cpu, "softclock %d", cpu); } } @@ -237,9 +237,9 @@ hardclock_softtick(globaldata_t gd) * a critical section is sufficient to interlock sc->curticks and protect * us from remote IPI's / list removal. * - * The thread starts with the MP lock held and not in a critical section. - * The loop itself is MP safe while individual callbacks may or may not - * be, so we obtain or release the MP lock as appropriate. + * The thread starts with the MP lock released and not in a critical + * section. The loop itself is MP safe while individual callbacks + * may or may not be, so we obtain or release the MP lock as appropriate. */ static void softclock_handler(void *arg) @@ -250,7 +250,7 @@ softclock_handler(void *arg) void (*c_func)(void *); void *c_arg; #ifdef SMP - int mpsafe = 0; + int mpsafe = 1; #endif lwkt_setpri_self(TDPRI_SOFT_NORM); diff --git a/sys/kern/lwkt_thread.c b/sys/kern/lwkt_thread.c index 346ffb04f8..1311f37dff 100644 --- a/sys/kern/lwkt_thread.c +++ b/sys/kern/lwkt_thread.c @@ -374,6 +374,9 @@ lwkt_init_thread(thread_t td, void *stack, int stksize, int flags, { globaldata_t mygd = mycpu; + /* all threads start mpsafe now */ + KKASSERT(flags & TDF_MPSAFE); + bzero(td, sizeof(struct thread)); td->td_kstack = stack; td->td_kstack_size = stksize; @@ -1570,9 +1573,8 @@ lwkt_preempted_proc(void) * rel_mplock() at the start of the new thread. */ int -lwkt_create(void (*func)(void *), void *arg, - struct thread **tdp, thread_t template, int tdflags, int cpu, - const char *fmt, ...) +lwkt_create(void (*func)(void *), void *arg, struct thread **tdp, + thread_t template, int tdflags, int cpu, const char *fmt, ...) { thread_t td; __va_list ap; diff --git a/sys/kern/subr_bus.c b/sys/kern/subr_bus.c index 26972e6976..312c29c759 100644 --- a/sys/kern/subr_bus.c +++ b/sys/kern/subr_bus.c @@ -1665,8 +1665,7 @@ device_probe_and_attach(device_t dev) /* * Device is known to be alive, do the attach asynchronously. - * - * The MP lock is held by all threads. + * However, serialize the attaches with the mp lock. */ static void device_attach_async(device_t dev) @@ -1675,7 +1674,8 @@ device_attach_async(device_t dev) atomic_add_int(&numasyncthreads, 1); lwkt_create(device_attach_thread, dev, &td, NULL, - 0, 0, (dev->desc ? dev->desc : "devattach")); + TDF_MPSAFE, 0, + (dev->desc ? dev->desc : "devattach")); } static void @@ -1683,9 +1683,11 @@ device_attach_thread(void *arg) { device_t dev = arg; + get_mplock(); /* XXX replace with devattach_token later */ (void)device_doattach(dev); atomic_subtract_int(&numasyncthreads, 1); wakeup(&numasyncthreads); + rel_mplock(); /* XXX replace with devattach_token later */ } /* diff --git a/sys/kern/subr_disk.c b/sys/kern/subr_disk.c index 81f3a67966..b9e84e1564 100644 --- a/sys/kern/subr_disk.c +++ b/sys/kern/subr_disk.c @@ -375,8 +375,12 @@ disk_msg_core(void *arg) disk_msg_t msg; int run; + lwkt_gettoken(&disklist_token); lwkt_initport_thread(&disk_msg_port, curthread); - wakeup(curthread); + wakeup(curthread); /* synchronous startup */ + lwkt_reltoken(&disklist_token); + + get_mplock(); /* not mpsafe yet? */ run = 1; while (run) { @@ -1275,10 +1279,11 @@ disk_init(void) */ lwkt_initport_replyonly(&disk_dispose_port, disk_msg_autofree_reply); + lwkt_gettoken(&disklist_token); lwkt_create(disk_msg_core, /*args*/NULL, &td_core, NULL, - 0, 0, "disk_msg_core"); - + TDF_MPSAFE, 0, "disk_msg_core"); tsleep(td_core, 0, "diskcore", 0); + lwkt_reltoken(&disklist_token); } static void diff --git a/sys/kern/usched_bsd4.c b/sys/kern/usched_bsd4.c index ede271e278..8c208c2dfe 100644 --- a/sys/kern/usched_bsd4.c +++ b/sys/kern/usched_bsd4.c @@ -1070,10 +1070,13 @@ bsd4_setrunqueue_locked(struct lwp *lp) /* * For SMP systems a user scheduler helper thread is created for each * cpu and is used to allow one cpu to wakeup another for the purposes of - * scheduling userland threads from setrunqueue(). UP systems do not - * need the helper since there is only one cpu. We can't use the idle - * thread for this because we need to hold the MP lock. Additionally, - * doing things this way allows us to HLT idle cpus on MP systems. + * scheduling userland threads from setrunqueue(). + * + * UP systems do not need the helper since there is only one cpu. + * + * We can't use the idle thread for this because we might block. + * Additionally, doing things this way allows us to HLT idle cpus + * on MP systems. * * MPSAFE */ @@ -1096,11 +1099,9 @@ sched_thread(void *dummy) dd = &bsd4_pcpu[cpuid]; /* - * The scheduler thread does not need to hold the MP lock. Since we - * are woken up only when no user processes are scheduled on a cpu, we - * can run at an ultra low priority. + * Since we are woken up only when no user processes are scheduled + * on a cpu, we can run at an ultra low priority. */ - rel_mplock(); lwkt_setpri_self(TDPRI_USER_SCHEDULER); for (;;) { @@ -1194,7 +1195,7 @@ sched_thread_cpu_init(void) kprintf(" %d", i); lwkt_create(sched_thread, NULL, NULL, &dd->helper_thread, - TDF_STOPREQ, i, "usched %d", i); + TDF_STOPREQ | TDF_MPSAFE, i, "usched %d", i); /* * Allow user scheduling on the target cpu. cpu #0 has already diff --git a/sys/kern/usched_dummy.c b/sys/kern/usched_dummy.c index aff357e1ca..fb22eb8dcb 100644 --- a/sys/kern/usched_dummy.c +++ b/sys/kern/usched_dummy.c @@ -455,6 +455,8 @@ dummy_exiting(struct lwp *plp, struct lwp *lp) * is possible to deschedule an LWKT thread and then do some work before * switching away. The thread can be rescheduled at any time, even before * we switch away. + * + * MPSAFE */ #ifdef SMP @@ -474,11 +476,6 @@ dummy_sched_thread(void *dummy) dd = &dummy_pcpu[cpuid]; cpumask = 1 << cpuid; - /* - * Our Scheduler helper thread does not need to hold the MP lock - */ - rel_mplock(); - for (;;) { lwkt_deschedule_self(gd->gd_curthread); /* interlock */ atomic_set_int(&dummy_rdyprocmask, cpumask); @@ -540,7 +537,7 @@ dummy_sched_thread_cpu_init(void) kprintf(" %d", i); lwkt_create(dummy_sched_thread, NULL, NULL, &dd->helper_thread, - TDF_STOPREQ, i, "dsched %d", i); + TDF_STOPREQ | TDF_MPSAFE, i, "dsched %d", i); /* * Allow user scheduling on the target cpu. cpu #0 has already diff --git a/sys/kern/vfs_journal.c b/sys/kern/vfs_journal.c index 0845f57369..f13c18429b 100644 --- a/sys/kern/vfs_journal.c +++ b/sys/kern/vfs_journal.c @@ -90,6 +90,7 @@ #include #include +#include #include static void journal_wthread(void *info); @@ -119,14 +120,16 @@ journal_create_threads(struct journal *jo) jo->flags &= ~(MC_JOURNAL_STOP_REQ | MC_JOURNAL_STOP_IMM); jo->flags |= MC_JOURNAL_WACTIVE; lwkt_create(journal_wthread, jo, NULL, &jo->wthread, - TDF_STOPREQ, -1, "journal w:%.*s", JIDMAX, jo->id); + TDF_STOPREQ | TDF_MPSAFE, + -1, "journal w:%.*s", JIDMAX, jo->id); lwkt_setpri(&jo->wthread, TDPRI_KERN_DAEMON); lwkt_schedule(&jo->wthread); if (jo->flags & MC_JOURNAL_WANT_FULLDUPLEX) { jo->flags |= MC_JOURNAL_RACTIVE; lwkt_create(journal_rthread, jo, NULL, &jo->rthread, - TDF_STOPREQ, -1, "journal r:%.*s", JIDMAX, jo->id); + TDF_STOPREQ | TDF_MPSAFE, + -1, "journal r:%.*s", JIDMAX, jo->id); lwkt_setpri(&jo->rthread, TDPRI_KERN_DAEMON); lwkt_schedule(&jo->rthread); } @@ -172,6 +175,9 @@ journal_wthread(void *info) size_t bytes; size_t res; + /* not MPSAFE yet */ + get_mplock(); + for (;;) { /* * Calculate the number of bytes available to write. This buffer @@ -288,6 +294,7 @@ journal_wthread(void *info) jo->flags &= ~MC_JOURNAL_WACTIVE; wakeup(jo); wakeup(&jo->fifo.windex); + rel_mplock(); } /* @@ -308,6 +315,9 @@ journal_rthread(void *info) transid = 0; error = 0; + /* not MPSAFE yet */ + get_mplock(); + for (;;) { /* * We have been asked to stop @@ -403,6 +413,7 @@ journal_rthread(void *info) jo->flags &= ~MC_JOURNAL_RACTIVE; wakeup(jo); wakeup(&jo->fifo.windex); + rel_mplock(); } /* diff --git a/sys/net/route.c b/sys/net/route.c index 712973e6af..c7d8b5ec1c 100644 --- a/sys/net/route.c +++ b/sys/net/route.c @@ -90,6 +90,7 @@ #include #include +#include #include #ifdef MPLS @@ -153,7 +154,7 @@ route_init(void) for (cpu = 0; cpu < ncpus; cpu++) { lwkt_create(rtable_service_loop, NULL, &rtd, NULL, - 0, cpu, "rtable_cpu %d", cpu); + TDF_MPSAFE, cpu, "rtable_cpu %d", cpu); rt_ports[cpu] = &rtd->td_msgport; } } @@ -197,6 +198,8 @@ rtable_service_loop(void *dummy __unused) struct netmsg *netmsg; thread_t td = curthread; + get_mplock(); /* XXX is this mpsafe yet? */ + while ((netmsg = lwkt_waitport(&td->td_msgport, 0)) != NULL) { netmsg->nm_dispatch(netmsg); } diff --git a/sys/vfs/devfs/devfs_core.c b/sys/vfs/devfs/devfs_core.c index d31ce8c7e6..1db3c477af 100644 --- a/sys/vfs/devfs/devfs_core.c +++ b/sys/vfs/devfs/devfs_core.c @@ -39,18 +39,20 @@ #include #include #include -#include -#include #include #include #include -#include #include #include #include #include #include +#include +#include +#include +#include + MALLOC_DEFINE(M_DEVFS, "devfs", "Device File System (devfs) allocations"); DEVFS_DECLARE_CLONE_BITMAP(ops_id); /* @@ -1071,9 +1073,14 @@ devfs_msg_core(void *arg) { devfs_msg_t msg; - devfs_run = 1; lwkt_initport_thread(&devfs_msg_port, curthread); + + lockmgr(&devfs_lock, LK_EXCLUSIVE); + devfs_run = 1; wakeup(td_core); + lockmgr(&devfs_lock, LK_RELEASE); + + get_mplock(); /* mpsafe yet? */ while (devfs_run) { msg = (devfs_msg_t)lwkt_waitport(&devfs_msg_port, 0); @@ -1083,7 +1090,10 @@ devfs_msg_core(void *arg) devfs_msg_exec(msg); lwkt_replymsg(&msg->hdr, 0); } + + rel_mplock(); wakeup(td_core); + lwkt_exit(); } @@ -2362,12 +2372,12 @@ devfs_init(void) /* Initialize *THE* devfs lock */ lockinit(&devfs_lock, "devfs_core lock", 0, 0); - + lockmgr(&devfs_lock, LK_EXCLUSIVE); lwkt_create(devfs_msg_core, /*args*/NULL, &td_core, NULL, - 0, 0, "devfs_msg_core"); - + TDF_MPSAFE, 0, "devfs_msg_core"); while (devfs_run == 0) - tsleep(td_core, 0, "devfsc", 0); + lksleep(td_core, &devfs_lock, 0, "devfsc", 0); + lockmgr(&devfs_lock, LK_RELEASE); devfs_debug(DEVFS_DEBUG_DEBUG, "devfs_init finished\n"); } diff --git a/sys/vfs/nfs/nfs_iod.c b/sys/vfs/nfs/nfs_iod.c index b1547b4502..33cbd257cb 100644 --- a/sys/vfs/nfs/nfs_iod.c +++ b/sys/vfs/nfs/nfs_iod.c @@ -56,11 +56,12 @@ #include #include +#include #include +#include #include #include -#include #include "rpcv2.h" #include "nfsproto.h" @@ -71,6 +72,9 @@ #include "nfsnode.h" #include "nfsrtt.h" +/* + * nfs service connection reader thread + */ void nfssvc_iod_reader(void *arg) { @@ -79,6 +83,8 @@ nfssvc_iod_reader(void *arg) struct nfsreq *req; int error; + get_mplock(); + if (nmp->nm_rxstate == NFSSVC_INIT) nmp->nm_rxstate = NFSSVC_PENDING; crit_enter(); @@ -149,6 +155,8 @@ nfssvc_iod_reader(void *arg) } /* + * nfs service connection writer thread + * * The writer sits on the send side of the client's socket and * does both the initial processing of BIOs and also transmission * and retransmission of nfsreq's. @@ -165,8 +173,11 @@ nfssvc_iod_writer(void *arg) struct vnode *vp; nfsm_info_t info; + get_mplock(); + if (nmp->nm_txstate == NFSSVC_INIT) nmp->nm_txstate = NFSSVC_PENDING; + crit_enter(); for (;;) { if (nmp->nm_txstate == NFSSVC_WAITING) { diff --git a/sys/vfs/nfs/nfs_vfsops.c b/sys/vfs/nfs/nfs_vfsops.c index 88431aafd7..c291c7a8f6 100644 --- a/sys/vfs/nfs/nfs_vfsops.c +++ b/sys/vfs/nfs/nfs_vfsops.c @@ -1121,9 +1121,9 @@ mountnfs(struct nfs_args *argp, struct mount *mp, struct sockaddr *nam, * Start the reader and writer threads. */ lwkt_create(nfssvc_iod_reader, nmp, &nmp->nm_rxthread, - NULL, 0, rxcpu, "nfsiod_rx"); + NULL, TDF_MPSAFE, rxcpu, "nfsiod_rx"); lwkt_create(nfssvc_iod_writer, nmp, &nmp->nm_txthread, - NULL, 0, txcpu, "nfsiod_tx"); + NULL, TDF_MPSAFE, txcpu, "nfsiod_tx"); return (0); bad: -- 2.41.0