/*************************************************************************** * Copyright (C) 2012 by Terraneo Federico * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * As a special exception, if other files instantiate templates or use * * macros or inline functions from this file, or you compile this file * * and link it with other works to produce a work based on this file, * * this file does not by itself cause the resulting work to be covered * * by the GNU General Public License. However the source code for this * * file must still be made available in accordance with the GNU General * * Public License. This exception does not invalidate any other reasons * * why a work based on this file might be covered by the GNU General * * Public License. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include "sync.h" #include "process_pool.h" #include "process.h" #include "SystemMap.h" using namespace std; #ifdef WITH_PROCESSES namespace miosix { /** * Used to check if a pointer passed from userspace is aligned */ static bool aligned(void *x) { return (reinterpret_cast(x) & 0b11)==0; } /** * This class contains information on all the processes in the system */ class Processes { public: /** * \return the instance of this class (singleton) */ static Processes& instance(); ///Used to assign a new pid to a process pid_t pidCounter; ///Maps the pid to the Process instance. Includes zombie processes map processes; ///Uset to guard access to processes and pidCounter Mutex procMutex; ///Used to wait on process termination ConditionVariable genericWaiting; private: Processes() { ProcessBase *kernel=Thread::getCurrentThread()->getProcess(); assert(kernel->getPid()==0); processes[0]=kernel; } Processes(const Processes&); Processes& operator=(const Processes&); }; Processes& Processes::instance() { static Processes singleton; return singleton; } // // class Process // pid_t Process::create(const ElfProgram& program) { Processes& p=Processes::instance(); ProcessBase *parent=Thread::getCurrentThread()->proc; unique_ptr proc(new Process(program)); { Lock l(p.procMutex); proc->pid=getNewPid(); proc->ppid=parent->pid; parent->childs.push_back(proc.get()); p.processes[proc->pid]=proc.get(); } Thread *thr; thr=Thread::createUserspace(Process::start,0,Thread::DEFAULT,proc.get()); if(thr==0) { Lock l(p.procMutex); p.processes.erase(proc->pid); parent->childs.remove(proc.get()); throw runtime_error("Thread creation failed"); } //Cannot throw bad_alloc due to the reserve in Process's constructor. //This ensures we will never be in the uncomfortable situation where a //thread has already been created but there's no memory to list it //among the threads of a process proc->threads.push_back(thr); thr->wakeup(); //Actually start the thread, now that everything is set up pid_t result=proc->pid; proc.release(); //Do not delete the pointer return result; } pid_t Process::getppid(pid_t proc) { Processes& p=Processes::instance(); Lock l(p.procMutex); map::iterator it=p.processes.find(proc); if(it==p.processes.end()) return -1; return it->second->ppid; } pid_t Process::waitpid(pid_t pid, int* exit, int options) { Processes& p=Processes::instance(); Lock l(p.procMutex); ProcessBase *self=Thread::getCurrentThread()->proc; if(pid<=0) { //Wait for a generic child process if(self->zombies.empty() && (options & WNOHANG)) return 0; while(self->zombies.empty()) { if(self->childs.empty()) return -1; p.genericWaiting.wait(l); } Process *joined=self->zombies.front(); self->zombies.pop_front(); p.processes.erase(joined->pid); if(joined->waitCount!=0) errorHandler(UNEXPECTED); if(exit!=0) *exit=joined->exitCode; pid_t result=joined->pid; delete joined; return result; } else { //Wait on a specific child process map::iterator it=p.processes.find(pid); if(it==p.processes.end() || it->second->ppid!=self->pid || pid==self->pid) return -1; //Since the case when pid==0 has been singled out, this cast is safe Process *joined=static_cast(it->second); if(joined->zombie==false) { //Process hasn't terminated yet if(options & WNOHANG) return 0; joined->waitCount++; joined->waiting.wait(l); joined->waitCount--; if(joined->waitCount<0 || joined->zombie==false) errorHandler(UNEXPECTED); } pid_t result=-1; if(joined->waitCount==0) { result=joined->pid; if(exit!=0) *exit=joined->exitCode; self->zombies.remove(joined); p.processes.erase(joined->pid); delete joined; } return result; } } Process::~Process() {} Process::Process(const ElfProgram& program) : program(program), waitCount(0), zombie(false) { //This is required so that bad_alloc can never be thrown when the first //thread of the process will be stored in this vector threads.reserve(1); //Done here so if not enough memory the new process is not even created image.load(program); unsigned int elfSize=program.getElfSize(); unsigned int roundedSize=elfSize; if(elfSize(&_elf_pool_start); unsigned int *end=reinterpret_cast(&_elf_pool_end); unsigned int elfPoolSize=(end-start)*sizeof(int); elfPoolSize=MPUConfiguration::roundSizeForMPU(elfPoolSize); mpu=MPUConfiguration(start,elfPoolSize, image.getProcessBasePointer(),image.getProcessImageSize()); // mpu=MPUConfiguration(program.getElfBase(),roundedSize, // image.getProcessBasePointer(),image.getProcessImageSize()); } void *Process::start(void *argv) { //This function is never called with a kernel thread, so the cast is safe Process *proc=static_cast(Thread::getCurrentThread()->proc); if(proc==0) errorHandler(UNEXPECTED); unsigned int entry=proc->program.getEntryPoint(); Thread::setupUserspaceContext(entry,proc->image.getProcessBasePointer(), proc->image.getProcessImageSize()); bool running=true; do { miosix_private::SyscallParameters sp=Thread::switchToUserspace(); if(proc->fault.faultHappened()) { running=false; proc->exitCode=SIGSEGV; //Segfault #ifdef WITH_ERRLOG iprintf("Process %d terminated due to a fault\n" "* Code base address was 0x%x\n" "* Data base address was %p\n",proc->pid, proc->program.getElfBase(), proc->image.getProcessBasePointer()); proc->mpu.dumpConfiguration(); proc->fault.print(); #endif //WITH_ERRLOG } else running=proc->handleSvc(sp); if(Thread::testTerminate()) running=false; } while(running); { Processes& p=Processes::instance(); Lock l(p.procMutex); proc->zombie=true; list::iterator it; for(it=proc->childs.begin();it!=proc->childs.end();++it) (*it)->ppid=0; for(it=proc->zombies.begin();it!=proc->zombies.end();++it) (*it)->ppid=0; ProcessBase *kernel=p.processes[0]; kernel->childs.splice(kernel->childs.begin(),proc->childs); kernel->zombies.splice(kernel->zombies.begin(),proc->zombies); map::iterator it2=p.processes.find(proc->ppid); if(it2==p.processes.end()) errorHandler(UNEXPECTED); it2->second->childs.remove(proc); if(proc->waitCount>0) proc->waiting.broadcast(); else { it2->second->zombies.push_back(proc); p.genericWaiting.broadcast(); } } return 0; } bool Process::handleSvc(miosix_private::SyscallParameters sp) { try { switch(sp.getSyscallId()) { case SYS_EXIT: { exitCode=(sp.getFirstParameter() & 0xff)<<8; return false; } case SYS_WRITE: { int fd=sp.getFirstParameter(); void *ptr=reinterpret_cast(sp.getSecondParameter()); size_t size=sp.getThirdParameter(); if(mpu.withinForReading(ptr,size)) { ssize_t result=fileTable.write(fd,ptr,size); sp.setReturnValue(result); } else sp.setReturnValue(-EFAULT); break; } case SYS_READ: { int fd=sp.getFirstParameter(); void *ptr=reinterpret_cast(sp.getSecondParameter()); size_t size=sp.getThirdParameter(); if(mpu.withinForWriting(ptr,size)) { ssize_t result=fileTable.read(fd,ptr,size); sp.setReturnValue(result); } else sp.setReturnValue(-EFAULT); break; } case SYS_USLEEP: { sp.setReturnValue(usleep(sp.getFirstParameter())); break; } case SYS_OPEN: { const char *str; str=reinterpret_cast(sp.getFirstParameter()); int flags=sp.getSecondParameter(); if(mpu.withinForReading(str)) { int fd=fileTable.open(str,flags, (flags & O_CREAT) ? sp.getThirdParameter() : 0); sp.setReturnValue(fd); } else sp.setReturnValue(-EFAULT); break; } case SYS_CLOSE: { int result=fileTable.close(sp.getFirstParameter()); sp.setReturnValue(result); break; } case SYS_LSEEK: { //FIXME: need to pass and return a 64 bit parameter, //now it is truncated to 32 bit but this is wrong off_t result=fileTable.lseek(sp.getFirstParameter(), sp.getSecondParameter(),sp.getThirdParameter()); sp.setReturnValue(result); break; } case SYS_SYSTEM: { const char *str; str=reinterpret_cast(sp.getFirstParameter()); if(mpu.withinForReading(str)) { std::pair res; res=SystemMap::instance().getElfProgram(str); if(res.first==0 || res.second==0) { sp.setReturnValue(-1); } else { ElfProgram program(res.first,res.second); int ret=0; pid_t child=Process::create(program); Process::waitpid(child,&ret,0); sp.setReturnValue(WEXITSTATUS(ret)); } } else sp.setReturnValue(-EFAULT); break; } case SYS_FSTAT: { struct stat *pstat; pstat=reinterpret_cast(sp.getSecondParameter()); if(mpu.withinForWriting(pstat,sizeof(struct stat)) && aligned(pstat)) { int result=fileTable.fstat(sp.getFirstParameter(),pstat); sp.setReturnValue(result); } else sp.setReturnValue(-EFAULT); break; } case SYS_ISATTY: { int result=fileTable.isatty(sp.getFirstParameter()); sp.setReturnValue(result); break; } case SYS_STAT: { const char *str; str=reinterpret_cast(sp.getFirstParameter()); struct stat *pstat; pstat=reinterpret_cast(sp.getSecondParameter()); if(mpu.withinForReading(str) && mpu.withinForWriting(pstat,sizeof(struct stat)) && aligned(pstat)) { int result=fileTable.stat(str,pstat); sp.setReturnValue(result); } else sp.setReturnValue(-EFAULT); } case SYS_LSTAT: { const char *str; str=reinterpret_cast(sp.getFirstParameter()); struct stat *pstat; pstat=reinterpret_cast(sp.getSecondParameter()); if(mpu.withinForReading(str) && mpu.withinForWriting(pstat,sizeof(struct stat)) && aligned(pstat)) { int result=fileTable.lstat(str,pstat); sp.setReturnValue(result); } else sp.setReturnValue(-EFAULT); } case SYS_FCNTL: { int result=fileTable.fcntl(sp.getFirstParameter(), sp.getSecondParameter(),sp.getThirdParameter()); sp.setReturnValue(result); break; } case SYS_IOCTL: { //TODO: need a way to validate ARG break; } case SYS_GETDENTS: { int fd=sp.getFirstParameter(); void *ptr=reinterpret_cast(sp.getSecondParameter()); size_t size=sp.getThirdParameter(); if(mpu.withinForWriting(ptr,size)) { int result=fileTable.getdents(fd,ptr,size); sp.setReturnValue(result); } else sp.setReturnValue(-EFAULT); break; } case SYS_GETCWD: { char *buf=reinterpret_cast(sp.getFirstParameter()); size_t size=sp.getSecondParameter(); if(mpu.withinForWriting(buf,size)) { int result=fileTable.getcwd(buf,size); sp.setReturnValue(result); } else sp.setReturnValue(-EFAULT); break; } case SYS_CHDIR: { const char *str; str=reinterpret_cast(sp.getFirstParameter()); if(mpu.withinForReading(str)) { int result=fileTable.chdir(str); sp.setReturnValue(result); } else sp.setReturnValue(-EFAULT); break; } case SYS_MKDIR: { const char *str; str=reinterpret_cast(sp.getFirstParameter()); if(mpu.withinForReading(str)) { int result=fileTable.mkdir(str,sp.getSecondParameter()); sp.setReturnValue(result); } else sp.setReturnValue(-EFAULT); break; } case SYS_RMDIR: { const char *str; str=reinterpret_cast(sp.getFirstParameter()); if(mpu.withinForReading(str)) { int result=fileTable.rmdir(str); sp.setReturnValue(result); } else sp.setReturnValue(-EFAULT); break; } case SYS_UNLINK: { const char *str; str=reinterpret_cast(sp.getFirstParameter()); if(mpu.withinForReading(str)) { int result=fileTable.unlink(str); sp.setReturnValue(result); } else sp.setReturnValue(-EFAULT); break; } case SYS_RENAME: { const char *oldName, *newName; oldName=reinterpret_cast(sp.getFirstParameter()); newName=reinterpret_cast(sp.getSecondParameter()); if(mpu.withinForReading(oldName) && mpu.withinForReading(newName)) { int result=fileTable.rename(oldName,newName); sp.setReturnValue(result); } else sp.setReturnValue(-EFAULT); break; } default: exitCode=SIGSYS; //Bad syscall #ifdef WITH_ERRLOG iprintf("Unexpected syscall number %d\n",sp.getSyscallId()); #endif //WITH_ERRLOG return false; } } catch(exception& e) { sp.setReturnValue(-ENOMEM); } return true; } pid_t Process::getNewPid() { Processes& p=Processes::instance(); for(;;p.pidCounter++) { if(p.pidCounter<0) p.pidCounter=1; if(p.pidCounter==0) continue; //Zero is not a valid pid map::iterator it=p.processes.find(p.pidCounter); if(it!=p.processes.end()) continue; //Pid number already used return p.pidCounter++; } } } //namespace miosix #endif //WITH_PROCESSES