From 67d986f882a6d5944f27d94517c82b9a735bb928 Mon Sep 17 00:00:00 2001 From: David van Moolenbroek Date: Wed, 8 Jul 2009 17:16:53 +0000 Subject: [PATCH] PM cleanup: merge exit and coredump paths --- servers/pm/exec.c | 2 +- servers/pm/forkexit.c | 160 +++++++++++++++++++++++++++++------------- servers/pm/main.c | 71 +------------------ servers/pm/proto.h | 9 ++- servers/pm/signal.c | 136 ++--------------------------------- servers/pm/table.c | 2 +- servers/pm/trace.c | 2 +- servers/pm/utility.c | 4 +- 8 files changed, 127 insertions(+), 259 deletions(-) diff --git a/servers/pm/exec.c b/servers/pm/exec.c index 62dd5f678..7e7ff087a 100644 --- a/servers/pm/exec.c +++ b/servers/pm/exec.c @@ -162,7 +162,7 @@ int result; /* Use SIGILL signal that something went wrong */ rmp->mp_sigstatus = SIGILL; - pm_exit(rmp, 0, FALSE /*!for_trace*/); + exit_proc(rmp, 0, PM_EXIT); return; } setreply(rmp-mproc, result); diff --git a/servers/pm/forkexit.c b/servers/pm/forkexit.c index eae82993d..e93793e6e 100644 --- a/servers/pm/forkexit.c +++ b/servers/pm/forkexit.c @@ -7,11 +7,12 @@ * exits first, it continues to occupy a slot until the parent does a WAIT. * * The entry points into this file are: - * do_fork: perform the FORK system call - * do_pm_exit: perform the EXIT system call (by calling pm_exit()) - * pm_exit: actually do the exiting - * do_wait: perform the WAITPID or WAIT system call - * tell_parent: tell parent about the death of a child + * do_fork: perform the FORK system call + * do_fork_nb: special nonblocking version of FORK, for RS + * do_exit: perform the EXIT system call (by calling exit_proc()) + * exit_proc: actually do the exiting, and tell FS about it + * exit_restart: continue exiting a process after FS has replied + * do_waitpid: perform the WAITPID or WAIT system call */ #include "pm.h" @@ -26,7 +27,9 @@ #define LAST_FEW 2 /* last few slots reserved for superuser */ -FORWARD _PROTOTYPE (void cleanup, (register struct mproc *child) ); +FORWARD _PROTOTYPE (void zombify, (struct mproc *rmp) ); +FORWARD _PROTOTYPE (void tell_parent, (struct mproc *child) ); +FORWARD _PROTOTYPE (void cleanup, (register struct mproc *rmp) ); /*===========================================================================* * do_fork * @@ -173,35 +176,45 @@ PUBLIC int do_fork_nb() } /*===========================================================================* - * do_pm_exit * + * do_exit * *===========================================================================*/ -PUBLIC int do_pm_exit() +PUBLIC int do_exit() { -/* Perform the exit(status) system call. The real work is done by pm_exit(), +/* Perform the exit(status) system call. The real work is done by exit_proc(), * which is also called when a process is killed by a signal. */ - pm_exit(mp, m_in.status, FALSE /*!for_trace*/); + exit_proc(mp, m_in.status, PM_EXIT); return(SUSPEND); /* can't communicate from beyond the grave */ } /*===========================================================================* - * pm_exit * + * exit_proc * *===========================================================================*/ -PUBLIC void pm_exit(rmp, exit_status, for_trace) +PUBLIC void exit_proc(rmp, exit_status, exit_type) register struct mproc *rmp; /* pointer to the process to be terminated */ int exit_status; /* the process' exit status (for parent) */ -int for_trace; +int exit_type; /* one of PM_EXIT, PM_EXIT_TR, PM_DUMPCORE */ { /* A process is done. Release most of the process' possessions. If its * parent is waiting, release the rest, else keep the process slot and * become a zombie. */ register int proc_nr, proc_nr_e; - int parent_waiting, right_child, r; - pid_t pidarg, procgrp; + int parent_waiting, r; + pid_t procgrp; struct mproc *p_mp; clock_t user_time, sys_time; + /* Do not create core files for set uid execution */ + if (exit_type == PM_DUMPCORE && rmp->mp_realuid != rmp->mp_effuid) + exit_type = PM_EXIT; + + /* System processes are destroyed before informing FS, meaning that FS can + * not get their CPU state, so we can't generate a coredump for them either. + */ + if (exit_type == PM_DUMPCORE && (rmp->mp_flags & PRIV_PROC)) + exit_type = PM_EXIT; + proc_nr = (int) (rmp - mproc); /* get process slot number */ proc_nr_e = rmp->mp_endpoint; @@ -213,7 +226,7 @@ int for_trace; /* Do accounting: fetch usage times and accumulate at parent. */ if((r=sys_times(proc_nr_e, &user_time, &sys_time, NULL)) != OK) - panic(__FILE__,"pm_exit: sys_times failed", r); + panic(__FILE__,"exit_proc: sys_times failed", r); p_mp = &mproc[rmp->mp_parent]; /* process' parent */ p_mp->mp_child_utime += user_time + rmp->mp_child_utime; /* add user time */ @@ -227,7 +240,7 @@ int for_trace; */ sys_nice(proc_nr_e, PRIO_STOP); /* stop the process */ if((r=vm_willexit(proc_nr_e)) != OK) { - panic(__FILE__, "pm_exit: vm_willexit failed", r); + panic(__FILE__, "exit_proc: vm_willexit failed", r); } if (proc_nr_e == INIT_PROC_NR) @@ -240,16 +253,16 @@ int for_trace; { /* Tell FS about the exiting process. */ if (rmp->mp_fs_call != PM_IDLE) - panic(__FILE__, "pm_exit: not idle", rmp->mp_fs_call); - rmp->mp_fs_call= (for_trace ? PM_EXIT_TR : PM_EXIT); + panic(__FILE__, "exit_proc: not idle", rmp->mp_fs_call); + rmp->mp_fs_call= exit_type; r= notify(FS_PROC_NR); - if (r != OK) panic(__FILE__, "pm_exit: unable to notify FS", r); + if (r != OK) panic(__FILE__, "exit_proc: unable to notify FS", r); if (rmp->mp_flags & PRIV_PROC) { /* destroy system processes without waiting for FS */ if((r= sys_exit(rmp->mp_endpoint)) != OK) - panic(__FILE__, "pm_exit: sys_exit failed", r); + panic(__FILE__, "exit_proc: sys_exit failed", r); } } else @@ -264,18 +277,12 @@ int for_trace; /* Keep the process around until FS is finished with it. */ rmp->mp_exitstatus = (char) exit_status; - pidarg = p_mp->mp_wpid; /* who's being waited for? */ - parent_waiting = p_mp->mp_flags & WAITING; - right_child = /* child meets one of the 3 tests? */ - (pidarg == -1 || pidarg == rmp->mp_pid || -pidarg == rmp->mp_procgrp); - if (parent_waiting && right_child) { - tell_parent(rmp); /* tell parent */ - } else { - rmp->mp_flags &= (IN_USE|PRIV_PROC|HAS_DMA); - rmp->mp_flags |= ZOMBIE; /* parent not waiting, zombify child */ - sig_proc(p_mp, SIGCHLD); /* send parent a "child died" signal */ - } + /* For normal exits, try to notify the parent as soon as possible. + * For core dumps, notify the parent only once the core dump has been made. + */ + if (exit_type != PM_DUMPCORE) + zombify(rmp); /* If the process has children, disinherit them. INIT is the new parent. */ for (rmp = &mproc[0]; rmp < &mproc[NR_PROCS]; rmp++) { @@ -284,9 +291,11 @@ int for_trace; rmp->mp_parent = INIT_PROC_NR; parent_waiting = mproc[INIT_PROC_NR].mp_flags & WAITING; if (parent_waiting && (rmp->mp_flags & ZOMBIE) && - !(rmp->mp_flags & TOLD_PARENT) && - rmp->mp_fs_call != PM_EXIT) { - cleanup(rmp); + !(rmp->mp_flags & TOLD_PARENT)) { + tell_parent(rmp); + + if (rmp->mp_fs_call == PM_IDLE) + cleanup(rmp); } } } @@ -295,6 +304,46 @@ int for_trace; if (procgrp != 0) check_sig(-procgrp, SIGHUP); } +/*===========================================================================* + * exit_restart * + *===========================================================================*/ +PUBLIC void exit_restart(rmp, reply_type) +struct mproc *rmp; /* pointer to the process being terminated */ +int reply_type; /* one of PM_EXIT_REPLY(_TR), PM_CORE_REPLY */ +{ +/* FS replied to our exit or coredump request. Perform the second half of the + * exit code. + */ + int r; + + /* For core dumps, now is the right time to try to contact the parent. */ + if (reply_type == PM_CORE_REPLY) + zombify(rmp); + + if (!(rmp->mp_flags & PRIV_PROC)) + { + /* destroy the (user) process */ + if((r=sys_exit(rmp->mp_endpoint)) != OK) + panic(__FILE__, "exit_restart: sys_exit failed", r); + } + + /* Release the memory occupied by the child. */ + if((r=vm_exit(rmp->mp_endpoint)) != OK) { + panic(__FILE__, "exit_restart: vm_exit failed", r); + } + + if (reply_type == PM_EXIT_REPLY_TR && rmp->mp_parent != INIT_PROC_NR) + { + /* Wake up the parent, completing the ptrace(T_EXIT) call */ + mproc[rmp->mp_parent].mp_reply.reply_trace = 0; + setreply(rmp->mp_parent, OK); + } + + /* Clean up if the parent has collected the exit status */ + if (rmp->mp_flags & TOLD_PARENT) + cleanup(rmp); +} + /*===========================================================================* * do_waitpid * *===========================================================================*/ @@ -305,7 +354,7 @@ PUBLIC int do_waitpid() * really wait. * A process calling WAIT never gets a reply in the usual way at the end * of the main loop (unless WNOHANG is set or no qualifying child exists). - * If a child has already exited, the routine cleanup() sends the reply + * If a child has already exited, the routine tell_parent() sends the reply * to awaken the caller. * Both WAIT and WAITPID are handled by this code. */ @@ -335,7 +384,7 @@ PUBLIC int do_waitpid() /* This child meets the pid test and has exited. */ tell_parent(rp); /* this child has already exited */ if (rp->mp_fs_call == PM_IDLE) - real_cleanup(rp); + cleanup(rp); return(SUSPEND); } if ((rp->mp_flags & STOPPED) && rp->mp_sigstatus) { @@ -363,27 +412,40 @@ PUBLIC int do_waitpid() } /*===========================================================================* - * cleanup * + * zombify * *===========================================================================*/ -PRIVATE void cleanup(child) -register struct mproc *child; /* tells which process is exiting */ +PRIVATE void zombify(rmp) +struct mproc *rmp; { -/* Finish off the exit of a process. The process has exited or been killed - * by a signal, and its parent is waiting. +/* Zombify a process. If the parent is waiting, notify it immediately. + * Otherwise, send a SIGCHLD signal to the parent. */ + struct mproc *p_mp; + int parent_waiting, right_child; + pid_t pidarg; + + if (rmp->mp_flags & ZOMBIE) + panic(__FILE__, "zombify: process was already a zombie", NO_NUM); - if (child->mp_fs_call != PM_IDLE) - panic(__FILE__, "cleanup: not idle", child->mp_fs_call); + rmp->mp_flags &= (IN_USE|PRIV_PROC|HAS_DMA); + rmp->mp_flags |= ZOMBIE; - tell_parent(child); - real_cleanup(child); + p_mp = &mproc[rmp->mp_parent]; + pidarg = p_mp->mp_wpid; /* who's being waited for? */ + parent_waiting = p_mp->mp_flags & WAITING; + right_child = /* child meets one of the 3 tests? */ + (pidarg == -1 || pidarg == rmp->mp_pid || -pidarg == rmp->mp_procgrp); + if (parent_waiting && right_child) + tell_parent(rmp); /* tell parent */ + else + sig_proc(p_mp, SIGCHLD); /* send parent a "child died" signal */ } /*===========================================================================* * tell_parent * *===========================================================================*/ -PUBLIC void tell_parent(child) +PRIVATE void tell_parent(child) register struct mproc *child; /* tells which process is exiting */ { int exitstatus, mp_parent; @@ -405,9 +467,9 @@ register struct mproc *child; /* tells which process is exiting */ } /*===========================================================================* - * real_cleanup * + * cleanup * *===========================================================================*/ -PUBLIC void real_cleanup(rmp) +PRIVATE void cleanup(rmp) register struct mproc *rmp; /* tells which process is exiting */ { /* Release the process table entry and reinitialize some field. */ diff --git a/servers/pm/main.c b/servers/pm/main.c index 6ed92614e..130e06837 100644 --- a/servers/pm/main.c +++ b/servers/pm/main.c @@ -596,34 +596,7 @@ message *m_ptr; /* Call is finished */ rmp->mp_fs_call= PM_IDLE; - if (!(rmp->mp_flags & PRIV_PROC)) - { - /* destroy the (user) process */ - if((r=sys_exit(proc_e)) != OK) - { - panic(__FILE__, - "PM_EXIT_REPLY: sys_exit failed", r); - } - } - - /* Release the memory occupied by the child. */ - if((s=vm_exit(rmp->mp_endpoint)) != OK) { - panic(__FILE__, "vm_exit() failed", s); - } - - if (m_ptr->m_type == PM_EXIT_REPLY_TR && - rmp->mp_parent != INIT_PROC_NR) - { - /* Wake up the parent */ - mproc[rmp->mp_parent].mp_reply.reply_trace = 0; - setreply(rmp->mp_parent, OK); - } - - /* Clean up if the parent has collected the exit - * status - */ - if (rmp->mp_flags & TOLD_PARENT) - real_cleanup(rmp); + exit_restart(rmp, m_ptr->m_type); break; @@ -675,10 +648,6 @@ message *m_ptr; case PM_CORE_REPLY: { - int parent_waiting, right_child; - pid_t pidarg; - struct mproc *p_mp; - proc_e= m_ptr->PM_CORE_PROC; if (pm_isokendpt(proc_e, &proc_n) != OK) { @@ -694,43 +663,7 @@ message *m_ptr; /* Call is finished */ rmp->mp_fs_call= PM_IDLE; - p_mp = &mproc[rmp->mp_parent]; /* process' parent */ - pidarg = p_mp->mp_wpid; /* who's being waited for? */ - parent_waiting = p_mp->mp_flags & WAITING; - right_child = /* child meets one of the 3 tests? */ - (pidarg == -1 || pidarg == rmp->mp_pid || - -pidarg == rmp->mp_procgrp); - - if (parent_waiting && right_child) { - tell_parent(rmp); /* tell parent */ - } else { - /* parent not waiting, zombify child */ - rmp->mp_flags &= (IN_USE|PRIV_PROC|HAS_DMA); - rmp->mp_flags |= ZOMBIE; - /* send parent a "child died" signal */ - sig_proc(p_mp, SIGCHLD); - } - - if (!(rmp->mp_flags & PRIV_PROC)) - { - /* destroy the (user) process */ - if((r=sys_exit(proc_e)) != OK) - { - panic(__FILE__, - "PM_CORE_REPLY: sys_exit failed", r); - } - } - - /* Release the memory occupied by the child. */ - if((s=vm_exit(rmp->mp_endpoint)) != OK) { - panic(__FILE__, "vm_exit() failed", s); - } - - /* Clean up if the parent has collected the exit - * status - */ - if (rmp->mp_flags & TOLD_PARENT) - real_cleanup(rmp); + exit_restart(rmp, m_ptr->m_type); break; } diff --git a/servers/pm/proto.h b/servers/pm/proto.h index ef6e6045e..8504b0076 100644 --- a/servers/pm/proto.h +++ b/servers/pm/proto.h @@ -33,12 +33,11 @@ _PROTOTYPE( void exec_restart, (struct mproc *rmp, int result) ); /* forkexit.c */ _PROTOTYPE( int do_fork, (void) ); _PROTOTYPE( int do_fork_nb, (void) ); -_PROTOTYPE( int do_pm_exit, (void) ); +_PROTOTYPE( int do_exit, (void) ); _PROTOTYPE( int do_waitpid, (void) ); -_PROTOTYPE( void pm_exit, (struct mproc *rmp, int exit_status, - int for_trace) ); -_PROTOTYPE (void tell_parent, (struct mproc *child) ); -_PROTOTYPE( void real_cleanup, (struct mproc *rmp) ); +_PROTOTYPE( void exit_proc, (struct mproc *rmp, int exit_status, + int exit_type) ); +_PROTOTYPE( void exit_restart, (struct mproc *rmp, int reply_type) ); /* getset.c */ _PROTOTYPE( int do_getset, (void) ); diff --git a/servers/pm/signal.c b/servers/pm/signal.c index f80dc0ba4..f43e33830 100644 --- a/servers/pm/signal.c +++ b/servers/pm/signal.c @@ -34,7 +34,6 @@ #include "mproc.h" #include "param.h" -FORWARD _PROTOTYPE( int dump_core, (struct mproc *rmp) ); FORWARD _PROTOTYPE( void unpause, (int pro, int for_trace) ); FORWARD _PROTOTYPE( void handle_ksig, (int proc_nr, sigset_t sig_map) ); FORWARD _PROTOTYPE( void cause_sigalrm, (struct timer *tp) ); @@ -422,6 +421,7 @@ int signo; /* signal to send to process (1 to _NSIG) */ int s; int slot; int sigflags; + int exit_type; slot = (int) (rmp - mproc); if ((rmp->mp_flags & (IN_USE | ZOMBIE)) != IN_USE) { @@ -520,16 +520,13 @@ doterminate: } rmp->mp_sigstatus = (char) signo; + exit_type = PM_EXIT; if (sigismember(&core_sset, signo) && slot != FS_PROC_NR) { - printf("PM: signal %d for %d / %s\n", signo, rmp->mp_pid, rmp->mp_name); - s= dump_core(rmp); - if (s == SUSPEND) { - return; - } - - /* Not dumping core, just call exit */ + printf("PM: coredump signal %d for %d / %s\n", signo, rmp->mp_pid, + rmp->mp_name); + exit_type = PM_DUMPCORE; } - pm_exit(rmp, 0, FALSE /*!for_trace*/); /* terminate process */ + exit_proc(rmp, 0, exit_type); /* terminate process */ } /*===========================================================================* @@ -669,124 +666,3 @@ int for_trace; /* for tracing */ r= notify(FS_PROC_NR); if (r != OK) panic("pm", "unpause: unable to notify FS", r); } - -/*===========================================================================* - * dump_core * - *===========================================================================*/ -PRIVATE int dump_core(rmp) -register struct mproc *rmp; /* whose core is to be dumped */ -{ -/* Make a core dump on the file "core", if possible. */ - - int r, proc_nr, proc_nr_e, parent_waiting; - pid_t procgrp; -#if 0 - vir_bytes current_sp; -#endif - struct mproc *p_mp; - clock_t user_time, sys_time; - - printf("dumpcore for %d / %s\n", rmp->mp_pid, rmp->mp_name); - - /* Do not create core files for set uid execution */ - if (rmp->mp_realuid != rmp->mp_effuid) return OK; - - /* Make sure the stack segment is up to date. - * We don't want adjust() to fail unless current_sp is preposterous, - * but it might fail due to safety checking. Also, we don't really want - * the adjust() for sending a signal to fail due to safety checking. - * Maybe make SAFETY_BYTES a parameter. - */ -#if 0 - if ((r= get_stack_ptr(rmp->mp_endpoint, ¤t_sp)) != OK) - panic(__FILE__,"couldn't get new stack pointer (for core)", r); - adjust(rmp, rmp->mp_seg[D].mem_len, current_sp); -#endif - - /* Tell FS about the exiting process. */ - if (rmp->mp_fs_call != PM_IDLE) - panic(__FILE__, "dump_core: not idle", rmp->mp_fs_call); - rmp->mp_fs_call= PM_DUMPCORE; - r= notify(FS_PROC_NR); - if (r != OK) panic(__FILE__, "dump_core: unable to notify FS", r); - - /* Also perform most of the normal exit processing. Informing the parent - * has to wait until we know whether the coredump was successful or not. - */ - - proc_nr = (int) (rmp - mproc); /* get process slot number */ - proc_nr_e = rmp->mp_endpoint; - - /* Remember a session leader's process group. */ - procgrp = (rmp->mp_pid == mp->mp_procgrp) ? mp->mp_procgrp : 0; - - /* If the exited process has a timer pending, kill it. */ - if (rmp->mp_flags & ALARM_ON) set_alarm(proc_nr_e, (unsigned) 0); - - /* Do accounting: fetch usage times and accumulate at parent. */ - if((r=sys_times(proc_nr_e, &user_time, &sys_time, NULL)) != OK) - panic(__FILE__,"dump_core: sys_times failed", r); - - p_mp = &mproc[rmp->mp_parent]; /* process' parent */ - p_mp->mp_child_utime += user_time + rmp->mp_child_utime; /* add user time */ - p_mp->mp_child_stime += sys_time + rmp->mp_child_stime; /* add system time */ - - /* Tell the kernel the process is no longer runnable to prevent it from - * being scheduled in between the following steps. Then tell FS that it - * the process has exited and finally, clean up the process at the kernel. - * This order is important so that FS can tell drivers to cancel requests - * such as copying to/ from the exiting process, before it is gone. - */ - sys_nice(proc_nr_e, PRIO_STOP); /* stop the process */ - if((r=vm_willexit(proc_nr_e)) != OK) { - panic(__FILE__,"dump_core: vm_willexit failed", r); - } - - if(proc_nr_e != FS_PROC_NR) /* if it is not FS that is exiting.. */ - { - if (rmp->mp_flags & PRIV_PROC) - { - /* destroy system processes without waiting for FS */ - if((r= sys_exit(rmp->mp_endpoint)) != OK) - panic(__FILE__, "dump_core: sys_exit failed", r); - - /* Just send a SIGCHLD. Dealing with waidpid is too complicated - * here. - */ - p_mp = &mproc[rmp->mp_parent]; /* process' parent */ - sig_proc(p_mp, SIGCHLD); - - /* Zombify to avoid calling sys_endksig */ - rmp->mp_flags |= ZOMBIE; - } - } - else - { - printf("PM: FS died\n"); - return SUSPEND; - } - - /* Pending reply messages for the dead process cannot be delivered. */ - rmp->mp_flags &= ~REPLY; - - /* Keep the process around until FS is finished with it. */ - - /* If the process has children, disinherit them. INIT is the new parent. */ - for (rmp = &mproc[0]; rmp < &mproc[NR_PROCS]; rmp++) { - if (rmp->mp_flags & IN_USE && rmp->mp_parent == proc_nr) { - /* 'rmp' now points to a child to be disinherited. */ - rmp->mp_parent = INIT_PROC_NR; - parent_waiting = mproc[INIT_PROC_NR].mp_flags & WAITING; - if (parent_waiting && (rmp->mp_flags & ZOMBIE)) - { - tell_parent(rmp); - real_cleanup(rmp); - } - } - } - - /* Send a hangup to the process' process group if it was a session leader. */ - if (procgrp != 0) check_sig(-procgrp, SIGHUP); - - return SUSPEND; -} diff --git a/servers/pm/table.c b/servers/pm/table.c index d25867cc5..e1a1e2c42 100644 --- a/servers/pm/table.c +++ b/servers/pm/table.c @@ -15,7 +15,7 @@ char core_name[] = "core"; /* file name where core images are produced */ _PROTOTYPE (int (*call_vec[]), (void) ) = { no_sys, /* 0 = unused */ - do_pm_exit, /* 1 = exit */ + do_exit, /* 1 = exit */ do_fork, /* 2 = fork */ no_sys, /* 3 = read */ no_sys, /* 4 = write */ diff --git a/servers/pm/trace.c b/servers/pm/trace.c index ec7b35a9a..8e8622c95 100644 --- a/servers/pm/trace.c +++ b/servers/pm/trace.c @@ -112,7 +112,7 @@ PUBLIC int do_trace() switch (m_in.request) { case T_EXIT: /* exit */ - pm_exit(child, (int) m_in.data, TRUE /*for_trace*/); + exit_proc(child, (int) m_in.data, PM_EXIT_TR); /* Do not reply to the caller until FS has processed the exit * request. */ diff --git a/servers/pm/utility.c b/servers/pm/utility.c index ef751582d..a479ed461 100644 --- a/servers/pm/utility.c +++ b/servers/pm/utility.c @@ -3,11 +3,9 @@ * The entry points are: * find_param: look up a boot monitor parameter * get_free_pid: get a free process or group id - * allowed: see if an access is permitted * no_sys: called for invalid system call numbers - * panic: PM has run aground of a fatal error - * get_stack_ptr: get stack pointer of given process * proc_from_pid: return process pointer from pid number + * pm_isokendpt: check the validity of an endpoint */ #include "pm.h" -- 2.44.0