diff --git a/lisp/sunos-os.c b/lisp/sunos-os.c new file mode 100644 index 0000000000000000000000000000000000000000..c3354747004146ec036502502cf7eea35aba5be3 --- /dev/null +++ b/lisp/sunos-os.c @@ -0,0 +1,700 @@ +/* + * $Header: /Volumes/share2/src/cmucl/cvs2git/cvsroot/src/lisp/sunos-os.c,v 1.1 1992/09/08 20:32:23 wlott Exp $ + * + * OS-dependent routines. This file (along with os.h) exports an + * OS-independent interface to the operating system VM facilities. + * Suprisingly, this interface looks a lot like the Mach interface + * (but simpler in some places). For some operating systems, a subset + * of these functions will have to be emulated. + * + * This is the SunOS version. + * March 1991, Miles Bader <miles@cogsci.ed.ack.uk> & ted <ted@edu.NMSU> + * + */ + +/* #define DEBUG */ + +#include <stdio.h> + +#include <signal.h> +#include <sys/file.h> + +#include "os.h" + +/* block size must be larger than the system page size */ +#define SPARSE_BLOCK_SIZE (1<<15) +#define SPARSE_SIZE_MASK (SPARSE_BLOCK_SIZE-1) + +#define PROT_DEFAULT OS_VM_PROT_ALL + +#define OFFSET_NONE ((os_vm_offset_t)(~0)) + +#define EMPTYFILE "/tmp/empty" +#define ZEROFILE "/dev/zero" + +#define MAX_SEGS 64 + +extern char *getenv(); + +/* ---------------------------------------------------------------- */ + +#define ADJ_OFFSET(off,adj) (((off)==OFFSET_NONE) ? OFFSET_NONE : ((off)+(adj))) + +long os_vm_page_size=(-1); + +static struct segment { + os_vm_address_t start; /* note: start & length are expected to be on page */ + os_vm_size_t length; /* boundaries */ + long file_offset; + short mapped_fd; + short protection; +} addr_map[MAX_SEGS]; + +static int n_segments=0; + +static int zero_fd=(-1), empty_fd=(-1); + +static os_vm_address_t last_fault=0; +static os_vm_size_t real_page_size_difference=0; + +void os_init() +{ + char *empty_file=getenv("CMUCL_EMPTYFILE"); + + if(empty_file==NULL) + empty_file=EMPTYFILE; + + empty_fd=open(empty_file,O_RDONLY|O_CREAT); + unlink(empty_file); + + zero_fd=open(ZEROFILE,O_RDONLY); + + os_vm_page_size=getpagesize(); + + if(os_vm_page_size>OS_VM_DEFAULT_PAGESIZE){ + fprintf(stderr,"os_init: Pagesize too large (%d > %d)\n", + os_vm_page_size,OS_VM_DEFAULT_PAGESIZE); + exit(1); + }else{ + /* + * we do this because there are apparently dependencies on + * the pagesize being OS_VM_DEFAULT_PAGESIZE somewhere... + * but since the OS doesn't know we're using this restriction, + * we have to grovel around a bit to enforce it, thus anything + * that uses real_page_size_difference. + */ + real_page_size_difference=OS_VM_DEFAULT_PAGESIZE-os_vm_page_size; + os_vm_page_size=OS_VM_DEFAULT_PAGESIZE; + } +} + +/* ---------------------------------------------------------------- */ + +void seg_force_resident(struct segment *seg, + os_vm_address_t addr, + os_vm_size_t len) +{ + int prot=seg->protection; + + if(prot!=0){ + os_vm_address_t end=addr+len, touch=addr; + + while(touch<end){ + int contents=(*(char *)touch); + if(prot&OS_VM_PROT_WRITE) + (*(char *)touch)=contents; + touch=(os_vm_address_t)(((long)touch+SPARSE_BLOCK_SIZE)&~SPARSE_SIZE_MASK); + } + } +} + +static struct segment *seg_create_nomerge(addr,len,protection,mapped_fd,file_offset) +os_vm_address_t addr; +os_vm_size_t len; +int protection; +int mapped_fd; +{ + int n; + struct segment *seg; + + if(len==0) + return NULL; + + if(n_segments==MAX_SEGS){ + fprintf(stderr,"seg_create_nomerge: Out of segments\n"); + return NULL; + } + + for(n=n_segments, seg=addr_map; n>0; n--, seg++) + if(addr<seg->start){ + seg=(&addr_map[n_segments]); + while(n-->0){ + seg[0]=seg[-1]; + seg--; + } + break; + } + + n_segments++; + + seg->start=addr; + seg->length=len; + seg->protection=protection; + seg->mapped_fd=mapped_fd; + seg->file_offset=file_offset; + + return seg; +} + +/* returns the first segment containing addr */ +static struct segment *seg_find(addr) +os_vm_address_t addr; +{ + int n; + struct segment *seg; + + for(n=n_segments, seg=addr_map; n>0; n--, seg++) + if(seg->start<=addr && seg->start+seg->length>addr) + return seg; + + return NULL; +} + +/* returns TRUE if the range from addr to addr+len intersects with any segment */ +static boolean collides_with_seg_p(addr,len) +os_vm_address_t addr; +os_vm_size_t len; +{ + int n; + struct segment *seg; + os_vm_address_t end=addr+len; + + for(n=n_segments, seg=addr_map; n>0; n--, seg++) + if(seg->start>=end) + return FALSE; + else if(seg->start+seg->length>addr) + return TRUE; + + return FALSE; +} + +#define seg_last_p(seg) (((seg)-addr_map)>=n_segments-1) + +static void seg_destroy(seg) +struct segment *seg; +{ + if(seg!=NULL){ + int n; + + for(n=seg-addr_map+1; n<n_segments; n++){ + seg[0]=seg[1]; + seg++; + } + + n_segments--; + } +} + +static void seg_try_merge_next(seg) +struct segment *seg; +{ + struct segment *nseg=seg+1; + + if(!seg_last_p(seg) + && seg->start+seg->length==nseg->start + && seg->protection==nseg->protection + && seg->mapped_fd==nseg->mapped_fd + && ADJ_OFFSET(seg->file_offset,seg->length)==nseg->file_offset) + { + /* can merge with the next segment */ +#ifdef DEBUG + fprintf(stderr, + ";;; seg_try_merge: Merged 0x%08x[0x%08x] with 0x%08x[0x%08x]\n", + seg->start,seg->length,nseg->start,nseg->length); +#endif + + if(((long)nseg->start&SPARSE_SIZE_MASK)!=0){ + /* + * if not on a block boundary, we have to ensure both parts + * of a common block are in a known state + */ + seg_force_resident(seg,nseg->start-1,1); + seg_force_resident(nseg,nseg->start,1); + } + + seg->length+=nseg->length; + seg_destroy(nseg); + } +} + + +/* + * Try to merge seg with adjacent segments. + */ +static void seg_try_merge_adjacent(seg) +struct segment *seg; +{ + if(!seg_last_p(seg)) + seg_try_merge_next(seg); + if(seg>addr_map) + seg_try_merge_next(seg-1); +} + +static struct segment *seg_create(addr,len,protection,mapped_fd,file_offset) +os_vm_address_t addr; +os_vm_size_t len; +int protection; +int mapped_fd; +{ + struct segment *seg=seg_create_nomerge(addr,len,protection,mapped_fd,file_offset); + if(seg!=NULL) + seg_try_merge_adjacent(seg); + return seg; +} + +/* + * Change the attributes of the given range of an existing segment, and return + * a segment corresponding to the new bit. + */ +static struct segment *seg_change_range(seg,addr,len,protection,mapped_fd,file_offset) +struct segment *seg; +os_vm_address_t addr; +os_vm_size_t len; +int protection; +int mapped_fd; +{ + os_vm_address_t end=addr+len; + + if(len==0) + return NULL; + + if(protection!=seg->protection + || mapped_fd!=seg->mapped_fd + || file_offset!=ADJ_OFFSET(seg->file_offset,addr-seg->start)) + { + os_vm_size_t old_len=seg->length, seg_offset=(addr-seg->start); + + if(old_len<len+seg_offset){ + struct segment *next=seg+1; + +#ifdef DEBUG + fprintf(stderr, + ";;; seg_change_range: region 0x%08x[0x%08x] overflows 0x%08x[0x%08x]\n", + addr,len, + seg->start,old_len); +#endif + + while(!seg_last_p(seg) && next->start+next->length<=end){ +#ifdef DEBUG + fprintf(stderr, + ";;; seg_change_range: merging extra segment 0x%08x[0x%08x]\n", + next->start, + next->length); +#endif + seg_destroy(next); + } + + if(!seg_last_p(seg) && next->start<end){ + next->length-=end-next->start; + next->start=end; + old_len=next->start-seg->start; + }else + old_len=len+seg_offset; + +#ifdef DEBUG + fprintf(stderr, + ";;; seg_change_range: extended first seg to 0x%08x[0x%08x]\n", + seg->start, + old_len); +#endif + } + + if(seg_offset+len<old_len){ + /* add second part of old segment */ + seg_create_nomerge(end, + old_len-(seg_offset+len), + seg->protection, + seg->mapped_fd, + ADJ_OFFSET(seg->file_offset,seg_offset+len)); + +#ifdef DEBUG + fprintf(stderr, + ";;; seg_change_range: Split off end of 0x%08x[0x%08x]: 0x%08x[0x%08x]\n", + seg->start,old_len, + end,old_len-(seg_offset+len)); +#endif + } + + if(seg_offset==0){ + seg->length=len; + seg->protection=protection; + seg->mapped_fd=mapped_fd; + seg->file_offset=file_offset; + }else{ + /* adjust first part of remaining old segment */ + seg->length=seg_offset; + +#ifdef DEBUG + fprintf(stderr, + ";;; seg_change_range: Split off beginning of 0x%08x[0x%08x]: 0x%08x[0x%08x]\n", + seg->start,old_len, + seg->start,seg_offset); +#endif + + /* add new middle segment for new protected region */ + seg=seg_create_nomerge(addr,len,protection,mapped_fd,file_offset); + } + + seg_try_merge_adjacent(seg); + + last_fault=0; + } + + return seg; +} + +/* ---------------------------------------------------------------- */ + +static os_vm_address_t mapin(addr,len,protection,map_fd,offset,is_readable) +os_vm_address_t addr; +os_vm_size_t len; +int protection; +int map_fd; +long offset; +int is_readable; +{ + os_vm_address_t real; + boolean sparse=(len>=SPARSE_BLOCK_SIZE); + + if(offset!=OFFSET_NONE + && (offset<os_vm_page_size || (offset&(os_vm_page_size-1))!=0)) + { + fprintf(stderr, + "mapin: file offset (%d) not multiple of pagesize (%d)\n", + offset, + os_vm_page_size); + } + + if(addr==NULL) + len+=real_page_size_difference; /* futz around to get an aligned region */ + + last_fault=0; + real=(os_vm_address_t) + mmap((caddr_t)addr, + (long)len, + sparse ? (is_readable ? PROT_READ|PROT_EXEC : 0) : protection, + (addr==NULL? 0 : MAP_FIXED)|MAP_PRIVATE, + (is_readable || !sparse) ? map_fd : empty_fd, + (off_t)(offset==OFFSET_NONE ? 0 : offset)); + + if((long)real==-1){ + perror("mapin: mmap"); + return NULL; + } + + if(addr==NULL){ + /* + * now play around with what the os gave us to make it align by + * our standards (which is why we overallocated) + */ + os_vm_size_t overflow; + + addr=os_round_up_to_page(real); + if(addr!=real) + munmap(real,addr-real); + + overflow=real_page_size_difference-(addr-real); + if(overflow!=0) + munmap(addr+len-real_page_size_difference,overflow); + + real=addr; + } + + + return real; +} + +static os_vm_address_t map_and_remember(addr,len,protection,map_fd,offset,is_readable) +os_vm_address_t addr; +os_vm_size_t len; +int protection; +int map_fd; +long offset; +int is_readable; +{ + os_vm_address_t real=mapin(addr,len,protection,map_fd,offset,is_readable); + + if(real!=NULL){ + struct segment *seg=seg_find(real); + + if(seg!=NULL) + seg=seg_change_range(seg,real,len,protection,map_fd,offset); + else + seg=seg_create(real,len,protection,map_fd,offset); + + if(seg==NULL){ + munmap(real,len); + return NULL; + } + } + +#ifdef DEBUG + fprintf(stderr,";;; map_and_remember: 0x%08x[0x%08x] offset: %d, mapped to: %d\n", + real,len,offset,map_fd); +#endif + + return real; +} + +/* ---------------------------------------------------------------- */ + +os_vm_address_t os_validate(addr, len) +os_vm_address_t addr; +os_vm_size_t len; +{ + addr=os_trunc_to_page(addr); + len=os_round_up_size_to_page(len); + +#ifdef DEBUG + fprintf(stderr, ";;; os_validate: 0x%08x[0x%08x]\n",addr,len); +#endif + + if(addr!=NULL && collides_with_seg_p(addr,len)) + return NULL; + + return map_and_remember(addr,len,PROT_DEFAULT,zero_fd,OFFSET_NONE,FALSE); +} + +void os_invalidate(addr, len) +os_vm_address_t addr; +os_vm_size_t len; +{ + struct segment *seg=seg_find(addr); + + addr=os_trunc_to_page(addr); + len=os_round_up_size_to_page(len); + +#ifdef DEBUG + fprintf(stderr, ";;; os_invalidate: 0x%08x[0x%08x]\n",addr,len); +#endif + + if(seg==NULL) + fprintf(stderr, "os_invalidate: Unknown segment: 0x%08x[0x%08x]\n",addr,len); + else{ + seg=seg_change_range(seg,addr,len,0,0,OFFSET_NONE); + if(seg!=NULL) + seg_destroy(seg); + + last_fault=0; + if(munmap(addr,len)!=0) + perror("os_invalidate: munmap"); + } +} + +os_vm_address_t os_map(fd, offset, addr, len) +int fd; +int offset; +os_vm_address_t addr; +long len; +{ + addr=os_trunc_to_page(addr); + len=os_round_up_size_to_page(len); + +#ifdef DEBUG + fprintf(stderr, ";;; os_map: 0x%08x[0x%08x]\n",addr,len); +#endif + + return map_and_remember(addr,len,PROT_DEFAULT,fd,offset,TRUE); +} + +void os_flush_icache(address, length) +os_vm_address_t address; +os_vm_size_t length; +{ +#if defined(MACH) && defined(mips) + vm_machine_attribute_val_t flush; + kern_return_t kr; + + flush = MATTR_VAL_ICACHE_FLUSH; + + kr = vm_machine_attribute(task_self(), address, length, + MATTR_CACHE, &flush); + if (kr != KERN_SUCCESS) + mach_error("Could not flush the instruction cache", kr); +#endif +} + +void os_protect(addr, len, prot) +os_vm_address_t addr; +os_vm_size_t len; +int prot; +{ + struct segment *seg=seg_find(addr); + + addr=os_trunc_to_page(addr); + len=os_round_up_size_to_page(len); + +#ifdef DEBUG + fprintf(stderr,";;; os_protect: 0x%08x[0x%08x]\n",addr,len); +#endif + + if(seg!=NULL){ + int old_prot=seg->protection; + + if(prot!=old_prot){ + /* + * oooooh, sick: we have to make sure all the pages being protected have + * faulted in, so they're in a known state... + */ + seg_force_resident(seg,addr,len); + + seg_change_range(seg,addr,len,prot,seg->mapped_fd,seg->file_offset); + + if(mprotect((caddr_t)addr,(long)len,prot)!=0) + perror("os_unprotect: mprotect"); + } + }else + fprintf(stderr,"os_protect: Unknown segment: 0x%08x[0x%08x]\n",addr,len); +} + +boolean valid_addr(test) +os_vm_address_t test; +{ + return seg_find(test)!=NULL; +} + +/* ---------------------------------------------------------------- */ + +static boolean maybe_gc(sig, code, context) +int sig, code; +struct sigcontext *context; +{ + /* + * It's necessary to enable recursive SEGVs, since the handle is + * used for multiple things (e.g., both gc-trigger & faulting in pages). + * We check against recursive gc's though... + */ + + boolean did_gc; + static already_trying=0; + + if(already_trying) + return FALSE; + + sigsetmask(context->sc_mask); + + already_trying=TRUE; + did_gc=interrupt_maybe_gc(sig, code, context); + already_trying=FALSE; + + return did_gc; +} + +/* + * The primary point of catching segmentation violations is to allow + * read only memory to be re-mapped with more permissions when a write + * is attempted. this greatly decreases the residency of the program + * in swap space since read only areas don't take up room + * + * Running into the gc trigger page will also end up here... + */ +void segv_handler(sig, code, context, addr) +int sig, code; +struct sigcontext *context; +caddr_t addr; +{ + if (code == SEGV_PROT) { /* allow writes to this chunk */ + struct segment *seg=seg_find(addr); + + if(last_fault==addr){ + if(seg!=NULL && maybe_gc(sig, code, context)) + /* we just garbage collected */ + return; + else{ + /* a *real* protection fault */ + fprintf(stderr, + "segv_handler: Real protection violation: 0x%08x\n", + addr); + interrupt_handle_now(sig,code,context); + } + }else + last_fault=addr; + + if(seg!=NULL){ + int err; + /* round down to a page */ + os_vm_address_t block=(os_vm_address_t)((long)addr&~SPARSE_SIZE_MASK); + os_vm_size_t length=SPARSE_BLOCK_SIZE; + + if(block < seg->start){ + length-=(seg->start - block); + block=seg->start; + } + if(block+length > seg->start+seg->length) + length=seg->start+seg->length-block; + +#if 0 + /* unmap it. probably redundant. */ + if(munmap(block,length) == -1) + perror("segv_handler: munmap"); +#endif + + /* and remap it with more permissions */ + err=(int) + mmap(block, + length, + seg->protection, + MAP_PRIVATE|MAP_FIXED, + seg->mapped_fd, + seg->file_offset==OFFSET_NONE + ? 0 + : seg->file_offset+(block-seg->start)); + + if (err == -1) { + perror("segv_handler: mmap"); + interrupt_handle_now(sig,code,context); + } + } + else{ + fprintf(stderr, "segv_handler: 0x%08x not in any segment\n",addr); + interrupt_handle_now(sig,code,context); + } + } + /* + * note that we check for a gc-trigger hit even if it's not a PROT error + */ + else if(!maybe_gc(sig, code, context)){ + static int nomap_count=0; + + if(code==SEGV_NOMAP){ + if(nomap_count==0){ + fprintf(stderr, + "segv_handler: No mapping fault: 0x%08x\n",addr); + nomap_count++; + }else{ + /* + * There should be higher-level protection against stack + * overflow somewhere, but at least this prevents infinite + * puking of error messages... + */ + fprintf(stderr, + "segv_handler: Recursive no mapping fault (stack overflow?)\n"); + exit(-1); + } + }else if(SEGV_CODE(code)==SEGV_OBJERR){ + extern int errno; + errno=SEGV_ERRNO(code); + perror("segv_handler: Object error"); + } + + interrupt_handle_now(sig,code,context); + + if(code==SEGV_NOMAP) + nomap_count--; + } +} + +void os_install_interrupt_handlers() +{ + interrupt_install_low_level_handler(SIGSEGV,segv_handler); +}