Homepage
Demos
Overview
Downloads
Dev. Resources
Reference
Credits

RCRegion.cc

Go to the documentation of this file.
00001 #ifndef PLATFORM_APERIOS
00002 #include "RCRegion.h"
00003 #include "LockScope.h"
00004 #include "Shared/debuget.h"
00005 #include <unistd.h>
00006 #include <sstream>
00007 #include <sys/stat.h>
00008 #include <errno.h>
00009 
00010 #if TEKKOTSU_SHM_STYLE!=SYSV_SHM && TEKKOTSU_SHM_STYLE!=POSIX_SHM
00011 #  error Unknown TEKKOTSU_SHM_STYLE setting
00012 #endif
00013 
00014 #if TEKKOTSU_SHM_STYLE==SYSV_SHM
00015 #  include <sys/ipc.h>
00016 #  include <sys/shm.h>
00017 #elif TEKKOTSU_SHM_STYLE==POSIX_SHM
00018 #  include <sys/mman.h>
00019 #  include <sys/fcntl.h>
00020 #  ifdef USE_UNBACKED_SHM
00021 plist::Primitive<bool> RCRegion::useUniqueMemoryRegions(false);
00022 #  else
00023 plist::Primitive<std::string> RCRegion::shmRoot("/tmp/tekkotsu_sim/");
00024 plist::Primitive<bool> RCRegion::useUniqueMemoryRegions(true);
00025 #  endif
00026 pid_t RCRegion::rootPID(::getpid());
00027 #endif
00028 
00029 using namespace std;
00030 
00031 typedef LockScope<ProcessID::NumProcesses> AutoLock;
00032 
00033 key_t RCRegion::nextKey=1024;
00034 RCRegion::attachedRegions_t RCRegion::attachedRegions;
00035 bool RCRegion::isFaultShutdown=false;
00036 
00037 #if TEKKOTSU_SHM_STYLE==SYSV_SHM
00038 //under SYSV shared memory, the keys are just numbers, and it's just as likely that the conflicted
00039 //region belongs to an unrelated process as it is that the region is from a previous run -- so we
00040 //take the safe route and rename our own keys
00041 RCRegion::ConflictResolutionStrategy RCRegion::conflictStrategy=RCRegion::RENAME;
00042 
00043 #elif TEKKOTSU_SHM_STYLE==POSIX_SHM
00044 //under POSIX shared memory, the keys are names, so we can have some confidence that a region
00045 //with the same name is ours from a previous run, so we replace it to avoid leaking (although
00046 //it's still possible we're conflicting with another application, but good name choices should
00047 //mitigate this)
00048 RCRegion::ConflictResolutionStrategy RCRegion::conflictStrategy=RCRegion::REPLACE;
00049 
00050 #endif
00051 
00052 RCRegion * RCRegion::attach(const Identifier& rid) {
00053   attachedRegions_t::iterator it=attachedRegions.find(rid.key);
00054   if(it==attachedRegions.end())
00055     return new RCRegion(rid); // the constructor will add entry to attachedRegions
00056   else {
00057     ASSERTRETVAL((*it).second!=NULL,"ERROR: attached region is NULL!",NULL);
00058     (*it).second->AddReference();
00059     return (*it).second;
00060   }
00061 }
00062 
00063 void RCRegion::AddReference() {
00064   AutoLock autolock(*lock,ProcessID::getID());
00065   //cout << "AddReference " << id.shmid << ' ' << ProcessID::getID();
00066   references[ProcessID::getID()]++;
00067   references[ProcessID::NumProcesses]++;
00068   //cout << " counts are now:";
00069   //for(unsigned int i=0; i<ProcessID::NumProcesses+1; i++)
00070   //  cout << ' ' << references[i];
00071   //cout << endl;
00072 }
00073 
00074 void RCRegion::RemoveReference() {
00075   //cout << "RemoveReference " << id.key << ' ' << ProcessID::getID();
00076   if(references[ProcessID::getID()] == 0) {
00077     cerr << "Warning: RCRegion reference count underflow on " << id.key << " by " << ProcessID::getID() << "!  ";
00078     for(unsigned int i=0; i<ProcessID::NumProcesses+1; i++)
00079       cerr << ' ' << references[i];
00080     cerr << endl;
00081     return;
00082   }
00083   //if(MutexLockBase::getSemaphoreManager()!=NULL) //check in case this region contained the mutexman and SharedObject just destructed it
00084   lock->lock(ProcessID::getID());
00085   bool wasLastProcRef=(--references[ProcessID::getID()] == 0);
00086   bool wasLastAnyRef=(--references[ProcessID::NumProcesses] == 0);
00087   ASSERT(wasLastProcRef || !wasLastAnyRef,"global reference decremented beyond process reference");
00088   //if(MutexLockBase::getSemaphoreManager()!=NULL)
00089   lock->unlock();
00090   if(isFaultShutdown) {
00091     cerr << "Process " << ProcessID::getID() << " dereferenced " << id.key << ".  Counts are now:";
00092     for(unsigned int i=0; i<ProcessID::NumProcesses+1; i++)
00093       cerr << ' ' << references[i];
00094     cerr << endl;
00095   }
00096   if(wasLastProcRef) {
00097     //cout << " detach";
00098 #if TEKKOTSU_SHM_STYLE==SYSV_SHM
00099     if(shmdt(base)<0)
00100       perror("Warning: Region detach");
00101     base=NULL;
00102     references=NULL;
00103     if(wasLastAnyRef) {
00104       //cout << " delete" << endl;
00105       if(shmctl(id.shmid,IPC_RMID,NULL)<0)
00106         perror("Warning: Region delete");
00107     }
00108 #elif TEKKOTSU_SHM_STYLE==POSIX_SHM
00109     if(munmap(base,calcRealSize(id.size))<0) {
00110       perror("Warning: Shared memory unmap (munmap)");
00111     }
00112     base=NULL;
00113     references=NULL;
00114     if(wasLastAnyRef) {
00115       //cout << " delete" << endl;
00116       if(!unlinkRegion()) {
00117         int err=errno;
00118         if(isFaultShutdown && (err==EINVAL || err==ENOENT))
00119           //On a fault shutdown, we initially try to unlink everything right away,
00120           // so an error now is just confirmation that it worked
00121           cerr << "Region " << id.key << " appears to have been successfully unlinked" << endl;
00122         else {
00123           cerr << "Warning: Shared memory unlink (shm_unlink) of region " << id.key << " returned " << strerror(err);
00124           if(err==EINVAL || err==ENOENT)
00125             cerr << "\n         May have already been unlinked by a dying process.";
00126           cerr << endl;
00127         }
00128       } else if(isFaultShutdown)
00129         //That shouldn't have succeeded on a faultShutdown...
00130         cerr << "Region " << id.key << " appears to have been successfully unlinked (nonstandard)" << endl;
00131     }
00132 #else
00133 #  error "Unknown TEKKOTSU_SHM_STYLE setting"
00134 #endif
00135     delete this;
00136   }
00137   //cout << endl;
00138 }
00139 
00140 void RCRegion::AddSharedReference() {
00141   AutoLock autolock(*lock,ProcessID::getID());
00142   //cout << "AddSharedReference " << id.shmid << ' ' << ProcessID::getID();
00143   references[ProcessID::NumProcesses]++;
00144   //cout << " counts are now:";
00145   //for(unsigned int i=0; i<ProcessID::NumProcesses+1; i++)
00146   //  cout << ' ' << references[i];
00147   //cout << endl;
00148 }
00149 
00150 void RCRegion::RemoveSharedReference() {
00151   AutoLock autolock(*lock,ProcessID::getID());
00152   //cout << "RemoveSharedReference " << id.shmid << ' ' << ProcessID::getID();
00153   if(references[ProcessID::NumProcesses]==0) {
00154     cerr << "Warning: RCRegion shared reference count underflow on " << id.key << " by " << ProcessID::getID() << "!  ";
00155     for(unsigned int i=0; i<ProcessID::NumProcesses+1; i++)
00156       cerr << ' ' << references[i];
00157     cerr << endl;
00158     return;
00159   }
00160   references[ProcessID::NumProcesses]--;
00161   //cout << " counts are now:";
00162   //for(unsigned int i=0; i<ProcessID::NumProcesses+1; i++)
00163   //  cout << ' ' << references[i];
00164   //cout << endl;
00165 }
00166 
00167 
00168 void RCRegion::aboutToFork(ProcessID::ProcessID_t newID) {
00169   //cout << "RCRegion aboutToFork to " << newID << endl;
00170   attachedRegions_t::const_iterator it=attachedRegions.begin();
00171   for(; it!=attachedRegions.end(); ++it) {
00172     //cout << "Duplicating attachments for " << (*it).first;
00173     (*it).second->references[newID]=(*it).second->references[ProcessID::getID()];
00174     (*it).second->references[ProcessID::NumProcesses]+=(*it).second->references[newID];
00175     //cout << " counts are now:";
00176     //for(unsigned int i=0; i<ProcessID::NumProcesses+1; i++)
00177     //  cout << ' ' << (*it).second->references[i];
00178     //cout << endl;
00179   }
00180 }
00181 
00182 void RCRegion::faultShutdown() {
00183   if(isFaultShutdown) {
00184     cerr << "WARNING: RCRegion::faultShutdown() called again... ignoring" << endl;
00185     return;
00186   }
00187   isFaultShutdown=true;
00188   if(attachedRegions.size()==0) {
00189     cerr << "WARNING: RCRegion::faultShutdown() called without any attached regions (may be a good thing?)" << endl;
00190     return;
00191   }
00192 #if TEKKOTSU_SHM_STYLE==POSIX_SHM
00193   //this may not really work, but it's worth a last-ditch attempt
00194   //in case the reference counts are screwed up.
00195   attachedRegions_t::const_iterator it=attachedRegions.begin();
00196   for(; it!=attachedRegions.end(); ++it) {
00197     cerr << "RCRegion::faultShutdown(): Process " << ProcessID::getID() << " unlinking " << (*it).second->id.key << endl;
00198 #ifdef USE_UNBACKED_SHM
00199     shm_unlink(getQualifiedName((*it).second->id.key).c_str());
00200 #else
00201     unlink(getQualifiedName((*it).second->id.key).c_str());
00202 #endif
00203   }
00204 #endif
00205   for(unsigned int i=0; i<100; i++) {
00206     unsigned int attempts=ProcessID::NumProcesses;
00207     unsigned int lastSize=attachedRegions.size();
00208     while(attachedRegions.size()==lastSize && attempts-->0)
00209       (*attachedRegions.begin()).second->RemoveReference();
00210     if(attempts==-1U) {
00211       cout << "Warning: could not dereference " << attachedRegions.begin()->second->id.key << endl;
00212       attachedRegions.erase(attachedRegions.begin());
00213     }
00214     if(attachedRegions.size()==0)
00215       break;
00216   }
00217 }
00218 
00219 RCRegion::~RCRegion() {
00220   attachedRegions.erase(id.key);
00221   ASSERT(base==NULL,"destructed with attachment!");
00222   ASSERT(references==NULL,"destructed with local references!");
00223   //cout << "~RCRegion " << id.shmid << ' ' << ProcessID::getID() << endl;
00224 }
00225   
00226 unsigned int RCRegion::calcRealSize(unsigned int size) {
00227   size=((size+align-1)/align)*align; //round up for field alignment
00228   size+=extra; //add room for the reference count
00229   unsigned int pagesize=::getpagesize();
00230   unsigned int pages=(size+pagesize-1)/pagesize;
00231   return pages*pagesize; //round up to the nearest page
00232 }
00233 
00234 #if TEKKOTSU_SHM_STYLE==SYSV_SHM
00235 
00236 void RCRegion::init(size_t sz, key_t sug_key, bool create) {
00237   id.size=sz;
00238   sz=calcRealSize(sz);
00239   if(create) {
00240     int flags = 0666 | IPC_CREAT | IPC_EXCL;
00241     if(sug_key==IPC_PRIVATE) {
00242       if((id.shmid=shmget(sug_key, sz, flags)) < 0) {
00243         int err=errno;
00244         if(err != EEXIST) {
00245           cerr << "ERROR: Getting new private region " << key << " of size " << sz ": " << strerror(err) << " (shmget)" << endl;
00246           exit(EXIT_FAILURE);
00247         }
00248       }
00249       id.key=sug_key;
00250     } else {
00251       nextKey=sug_key;
00252       switch(conflictStrategy) {
00253         case RENAME:
00254           while((id.shmid=shmget(id.key=nextKey++, sz, flags)) < 0) {
00255             int err=errno;
00256             if(err != EEXIST) {
00257               cerr << "ERROR: Getting new region " << key << " of size " << sz ": " << strerror(err) << " (shmget)" << endl;
00258               exit(EXIT_FAILURE);
00259             }
00260           }
00261           break;
00262         case REPLACE:
00263           if((id.shmid=shmget(id.key=nextKey, sz, flags)) >= 0)
00264             break;
00265           int err=errno;
00266           if(err != EEXIST) {
00267             cerr << "ERROR: Getting new region " << key << " of size " << sz ": " << strerror(err) << " (shmget)" << endl;
00268             exit(EXIT_FAILURE);
00269           }
00270 #ifdef DEBUG
00271           cerr << "Warning: conflicted key " << key << ", attempting to replace\n"
00272              << "         (may have been leftover from a previous crash)" << endl;
00273 #endif
00274           if(shmctl(id.shmid,IPC_RMID,NULL)<0)
00275             perror("Warning: Region delete from conflict - is another simulator running?");
00276           //note fall-through from REPLACE into EXIT - only try delete once, and then recreate and exit if it fails again
00277         case EXIT: 
00278           if((id.shmid=shmget(id.key=nextKey, sz, flags)) < 0) {
00279             int err=errno;
00280             cerr << "ERROR: Getting new region " << key << " of size " << sz ": " << strerror(err) << " (shmget)" << endl;
00281             exit(EXIT_FAILURE);
00282           }
00283       }
00284     }
00285   } else {
00286     int flags = 0666;
00287     if((id.shmid=shmget(sug_key, sz, flags)) < 0) {
00288       int err=errno;
00289       cerr << "ERROR: Getting existing region " << key << " of size " << sz ": " << strerror(err) << " (shmget)" << endl;
00290       exit(EXIT_FAILURE);
00291     }
00292     id.key=sug_key;
00293   }
00294   //cout << "ATTACHING " << id.shmid << " NOW" << endl;
00295   base=static_cast<char*>(shmat(id.shmid, NULL, SHM_RND));
00296   int err=errno;
00297   //cout << "Base is " << (void*)base << endl;
00298   if (base == reinterpret_cast<char*>(-1)) {
00299     cerr << "ERROR: Attaching region " << key << " of size " << sz << ": " << strerror(err) << " (shmat)" << endl;
00300     if(shmctl(id.shmid,IPC_RMID,NULL)<0)
00301       perror("Region delete");
00302     exit(EXIT_FAILURE);
00303   }
00304   lock=reinterpret_cast<MutexLock<ProcessID::NumProcesses>*>(base+sz-sizeof(MutexLock<ProcessID::NumProcesses>));
00305   references=reinterpret_cast<unsigned int*>(base+sz-extra);
00306   if(create) {
00307     new (lock) MutexLock<ProcessID::NumProcesses>;
00308     AutoLock autolock(*lock,ProcessID::getID());
00309     for(unsigned int i=0; i<ProcessID::NumProcesses+1; i++)
00310       references[i]=0;
00311   }
00312   AddReference();
00313   attachedRegions[id.key]=this;
00314 }
00315 
00316 #elif TEKKOTSU_SHM_STYLE==POSIX_SHM
00317   
00318 std::string RCRegion::getQualifiedName(const std::string& key) {
00319 #ifdef USE_UNBACKED_SHM
00320   string idval="/";
00321 #else
00322   string idval=shmRoot;
00323 #endif
00324   if(useUniqueMemoryRegions) {
00325     char pidstr[10];
00326     snprintf(pidstr,10,"%d-",rootPID);
00327     idval+=pidstr;
00328   }
00329   idval+=key;
00330   return idval;
00331 }
00332 int RCRegion::openRegion(int mode) const {
00333 #ifdef USE_UNBACKED_SHM
00334   return shm_open(getQualifiedName().c_str(),mode,0666);
00335 #else
00336   return open(getQualifiedName().c_str(),mode,0666);
00337 #endif
00338 }
00339 bool RCRegion::unlinkRegion() const {
00340 #ifdef USE_UNBACKED_SHM
00341   return shm_unlink(getQualifiedName().c_str())==0;
00342 #else
00343   return unlink(getQualifiedName().c_str())==0;
00344 #endif
00345 }
00346 void RCRegion::init(size_t sz, const std::string& name, bool create) {
00347   id.size=sz; //size of requested region
00348   sz=calcRealSize(sz); //add some additional space for region lock and reference counts
00349 #ifndef USE_UNBACKED_SHM
00350   struct stat statbuf;
00351   //cout << "Checking " << shmRoot.substr(0,shmRoot.rfind('/')) << endl;
00352   if(stat(shmRoot.substr(0,shmRoot.rfind('/')).c_str(),&statbuf)) {
00353     for(string::size_type c=shmRoot.find('/',1); c!=string::npos; c=shmRoot.find('/',c+1)) {
00354       //cout << "Checking " << shmRoot.substr(0,c) << endl;
00355       if(stat(shmRoot.substr(0,c).c_str(),&statbuf)) {
00356         mkdir(shmRoot.substr(0,c).c_str(),0777);
00357       } else if(!(statbuf.st_mode&S_IFDIR)) {
00358         cerr << "*** ERROR " << shmRoot.substr(0,c) << " exists and is not a directory" << endl;
00359         cerr << "           Cannot create file-backed shared memory regions in " << shmRoot << endl;
00360         exit(EXIT_FAILURE);
00361       }
00362     }
00363     cout << "Created '" << shmRoot.substr(0,shmRoot.rfind('/')) << "' for file-backed shared memory storage" << endl;
00364   } else if(!(statbuf.st_mode&S_IFDIR)) {
00365     cerr << "*** ERROR " << shmRoot.substr(0,shmRoot.rfind('/')) << " exists and is not a directory" << endl;
00366     cerr << "           Cannot create file-backed shared memory regions with prefix " << shmRoot << endl;
00367     exit(EXIT_FAILURE);
00368   }
00369 #endif
00370   int fd;
00371   if(name.size()>=MAX_NAME_LEN)
00372     cerr << "*** WARNING RCRegion named " << name << " will be clipped to " << name.substr(0,MAX_NAME_LEN-1) << endl;
00373   strncpy(id.key,name.c_str(),MAX_NAME_LEN-1);
00374   id.key[MAX_NAME_LEN-1]='\0';
00375   if(create) {
00376     static unsigned int renameSN=0;
00377     switch(conflictStrategy) {
00378       case RENAME: {
00379         char origName[MAX_NAME_LEN];
00380         strncpy(origName,id.key,MAX_NAME_LEN);
00381         if((fd=openRegion(O_RDWR|O_CREAT|O_EXCL))>=0)
00382           break;
00383         do {
00384           int err=errno;
00385           if(err!=EEXIST) {
00386             cerr << "ERROR: Opening new region " << id.key << ": " << strerror(err) << " (shm_open)" << endl;
00387             exit(EXIT_FAILURE);
00388           }
00389           unsigned int p=snprintf(id.key,MAX_NAME_LEN,"%s-%d",origName,++renameSN);
00390           if(p>=MAX_NAME_LEN) {
00391             cerr << "ERROR: conflicted key " << origName << ", attempting to rename, but generated name is too long" << endl;
00392             exit(EXIT_FAILURE);
00393           }
00394           //id.key[MAX_NAME_LEN-1]='\0';
00395 #ifdef DEBUG
00396           cerr << "Warning: conflicted key " << origName << ", attempting to rename as " << id.key << "\n"
00397             << "         (may have been leftover from a previous crash)" << endl;
00398 #endif
00399         } while((fd=openRegion(O_RDWR|O_CREAT|O_EXCL))<0);
00400         break;
00401       }
00402       case REPLACE: {
00403         if((fd=openRegion(O_RDWR|O_CREAT|O_EXCL))>=0)
00404           break;
00405         int err=errno;
00406         if(err!=EEXIST) {
00407           cerr << "ERROR: Opening new region " << id.key << ": " << strerror(err) << " (shm_open)" << endl;
00408           exit(EXIT_FAILURE);
00409         }
00410 #ifdef DEBUG
00411         cerr << "Warning: conflicted key " << id.key << ", attempting to replace\n"
00412              << "         (may have been leftover from a previous crash)" << endl;
00413 #endif
00414         if(!unlinkRegion())
00415           perror("Warning: Shared memory unlink (shm_unlink)");
00416       }
00417       //note fall-through from REPLACE into EXIT - only try delete once, and then recreate and exit if it fails again
00418       case EXIT: {
00419         if((fd=openRegion(O_RDWR|O_CREAT|O_EXCL))<0) {
00420           int err=errno;
00421           cerr << "ERROR: Opening new region " << id.key << ": " << strerror(err) << " (shm_open)" << endl;
00422           if(err==EEXIST)
00423             cerr << "This error suggests a leaked memory region, perhaps from a bad crash on a previous run.\n"
00424               << "You may either be able to use shm_unlink to remove the region, or reboot.\n"
00425               << "Also make sure that no other copies of the simulator are already running." << endl;
00426           exit(EXIT_FAILURE);
00427         }
00428       }
00429     }
00430     if (ftruncate(fd,sz)<0) {
00431       int err=errno;
00432       cerr << "ERROR: Sizing region " << id.key << " to " << sz << ": " << strerror(err) << " (ftruncate)" << endl;
00433       if(close(fd)<0)
00434         perror("Warning: Closing temporary file descriptor from shm_open");
00435       if(!unlinkRegion())
00436         perror("Warning: Shared memory unlink (shm_unlink)");
00437       exit(EXIT_FAILURE);
00438     }
00439   } else {
00440     if((fd=openRegion(O_RDWR))<0) {
00441       int err=errno;
00442       cerr << "ERROR: Opening existing region " << id.key << ": " << strerror(err) << " (shm_open)" << endl;
00443       exit(EXIT_FAILURE);
00444     }
00445   }
00446   base=static_cast<char*>(mmap(NULL,sz,PROT_READ|PROT_WRITE,MAP_SHARED,fd,(off_t)0)); 
00447   int err=errno;
00448   if (base == reinterpret_cast<char*>(-1)) {
00449     cerr << "ERROR: Attaching region " << id.key << " of size " << sz << ": " << strerror(err) << " (mmap)" << endl;
00450     if(close(fd)<0)
00451       perror("Warning: Closing temporary file descriptor from shm_open");
00452     if(!unlinkRegion())
00453       perror("Warning: Shared memory unlink (shm_unlink)");
00454     exit(EXIT_FAILURE);
00455   }
00456   if(close(fd)<0) {
00457     perror("Warning: Closing temporary file descriptor from shm_open");
00458   }
00459   lock=reinterpret_cast<MutexLock<ProcessID::NumProcesses>*>(base+sz-sizeof(MutexLock<ProcessID::NumProcesses>));
00460   references=reinterpret_cast<unsigned int*>(base+sz-extra);
00461   if(create) {
00462     new (lock) MutexLock<ProcessID::NumProcesses>;
00463     AutoLock autolock(*lock,ProcessID::getID());
00464     for(unsigned int i=0; i<ProcessID::NumProcesses+1; i++)
00465       references[i]=0;
00466   }
00467   AddReference();
00468   attachedRegions[id.key]=this;
00469 }
00470 
00471 #else
00472 #  error "Unknown TEKKOTSU_SHM_STYLE setting"
00473 #endif
00474 
00475 /*
00476  class syserr : public std::exception {
00477 public:
00478 #if TEKKOTSU_SHM_STYLE==SYSV_SHM
00479    syserr(int errnum, const key_t& key, const std::string& msg) throw() : info()
00480 #elif TEKKOTSU_SHM_STYLE==POSIX_SHM
00481    syserr(int errnum, const std::string& key, const std::string& msg) throw() : info()
00482 #endif
00483  {
00484      stringstream tmp;
00485      tmp << "Exception regarding region " << key << ": " << msg << '(' << strerror(errnum) << ')';
00486      info=tmp.c_str();
00487  }
00488    virtual ~syserr() throw() {}
00489    virtual const char * what() const throw() { return info.c_str(); }
00490 protected:
00491       std::string info;
00492  };
00493  */
00494 
00495 /*! @file
00496 * @brief Implements RCRegion, which provides compatability with the OPEN-R type of the same name
00497 * @author ejt (Creator)
00498 *
00499 * $Author: ejt $
00500 * $Name: tekkotsu-2_4_1 $
00501 * $Revision: 1.3 $
00502 * $State: Exp $
00503 * $Date: 2005/06/23 20:17:54 $
00504 */
00505 
00506 #endif

Tekkotsu v2.4.1
Generated Tue Aug 16 16:32:48 2005 by Doxygen 1.4.4