>From 481067ba2ad4813548e134a0f8108dfec2534aa3 Mon Sep 17 00:00:00 2001 From: Daniel Santos Date: Thu, 19 Jan 2012 04:01:01 -0600 Subject: Hack for in-process wine-server (a.k.a., "Dirty speed hack") MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="------------1.7.3.4" This is a multi-part message in MIME format. --------------1.7.3.4 Content-Type: text/plain; charset=UTF-8; format=fixed Content-Transfer-Encoding: 8bit --- dlls/ntdll/server.c | 133 +++++++++++++++++++++++++++++++++++++++++++++++-- dlls/ntdll/sync.c | 2 +- include/wine/server.h | 1 + server/Makefile.in | 32 +++++++++++- server/fd.c | 21 ++++++++ server/main.c | 17 ++++++- server/object.h | 11 ++++ server/request.c | 87 ++++++++++++++++++++++++++++++++ 8 files changed, 296 insertions(+), 8 deletions(-) --------------1.7.3.4 Content-Type: text/x-patch; name="0001-Hack-for-in-process-wine-server-a.k.a.-Dirty-speed-hac.txt" Content-Transfer-Encoding: 8bit Content-Disposition: attachment; filename="0001-Hack-for-in-process-wine-server-a.k.a.-Dirty-speed-hac.txt" diff --git a/dlls/ntdll/server.c b/dlls/ntdll/server.c index 8a01d22..3f7a957 100644 --- a/dlls/ntdll/server.c +++ b/dlls/ntdll/server.c @@ -74,6 +74,31 @@ WINE_DEFAULT_DEBUG_CHANNEL(server); +/************************************************************************************************** + * In-Process Server (a.k.a., "dirty speed hack") Laundry List + * + * - Build libwineserver rather or not --with-wine-tools is specified. It's currently built only + * when the server is built, but we need both 64 & 32 bit versions to run PEs of either type. The + * make targets (currently in server/Makefile.in) should probably be moved to + * libs/libwineserver/Makefile.in or other autotools code modofied so it's built independent of + * the wineserver. + * - Properly terminate wineserver when primary program exits. (Currently, auxiliary processes + * stick around and have to be manually killed after the main process/wineserver terminates. Also + * lock and socket files have to be manually cleaned up. + * - Use more portable synchronization mechanism in new server code (currently, we're just directly + * including pthread.h and using a pthread mutex). + * - Discover any other server calls (aside from init_thread and select) that should only be called + * via the pipe and change their wine_server_call function to wine_server_call_pipe. + * - Get rid of all fatal_{,p}error calls made outside of initialization. + * - Properly document new functions & data. + * + * Potential enhancements + * - Add at least one alternate server calling convention to reduce memcpy calls. + * - Tweak in-proc server call to copy less data (if possible without the above). + * - If PE is 32 bit, it must run a 32 bit wineserver and visa versa. This can create problems if + * the WINEPREFIX is a different arch (32 vs 64). The solution is probably to thunk. + *************************************************************************************************/ + /* Some versions of glibc don't define this */ #ifndef SCM_RIGHTS #define SCM_RIGHTS 1 @@ -102,6 +127,7 @@ static const enum cpu_type client_cpu = CPU_ARM; unsigned int server_cpus = 0; int is_wow64 = FALSE; +void (*call_req_handler_inproc)(void *req) = 0; timeout_t server_start_time = 0; /* time of server startup */ @@ -284,6 +310,25 @@ unsigned int wine_server_call( void *req_ptr ) sigset_t old_set; unsigned int ret; + if(call_req_handler_inproc) + { + call_req_handler_inproc(req); + return req->u.reply.reply_header.error; + } + + pthread_sigmask( SIG_BLOCK, &server_block_set, &old_set ); + ret = send_request( req ); + if (!ret) ret = wait_reply( req ); + pthread_sigmask( SIG_SETMASK, &old_set, NULL ); + return ret; +} + +unsigned int wine_server_call_pipe( void *req_ptr ) +{ + struct __server_request_info * const req = req_ptr; + sigset_t old_set; + unsigned int ret; + pthread_sigmask( SIG_BLOCK, &server_block_set, &old_set ); ret = send_request( req ); if (!ret) ret = wait_reply( req ); @@ -291,6 +336,13 @@ unsigned int wine_server_call( void *req_ptr ) return ret; } +unsigned int wine_server_call_inproc( void *req_ptr ) +{ + struct __server_request_info * const req = req_ptr; + + call_req_handler_inproc(req); + return req->u.reply.reply_header.error; +} /*********************************************************************** * server_enter_uninterrupted_section @@ -686,6 +738,11 @@ int server_pipe( int fd[2] ) return ret; } +/* Theory: start_server should only get called by one wine process (hopefully) + * so we don't have to screw with not loading the libwineserver.so in each + * process (therefore, screwing everythign up) + */ + /*********************************************************************** * start_server @@ -695,11 +752,14 @@ int server_pipe( int fd[2] ) static void start_server(void) { static int started; /* we only try once */ - char *argv[3]; + static char *argv[4]; static char wineserver[] = "server/wineserver"; static char debug[] = "-d"; + static char foreground[] = "-f"; - if (!started) + if (started) return; + + if(!getenv("WINESPEEDHACK")) { int status; int pid = fork(); @@ -716,8 +776,71 @@ static void start_server(void) status = WIFEXITED(status) ? WEXITSTATUS(status) : 1; if (status == 2) return; /* server lock held by someone else, will retry later */ if (status) exit(status); /* server failed */ - started = 1; } + else + { + char errmsg[1024]; + static void *ws_lib_handle; + void *(*ws_start)(void *); + pthread_t ws_thread; + struct timespec wait_time = {0, 500}; + void *wineserver_ret; +#if 0 /* do we need to try to boost wineserver's thread priority on gnu/linux? */ + pthread_attr_t attr; + struct sched_param sched_param; + int sched_pol = sched_getscheduler(getpid()); + int sched_max_pri = sched_get_priority_max(sched_pol); +#endif + + argv[0] = wineserver; + argv[1] = foreground; + argv[2] = TRACE_ON(server) ? debug : NULL; + argv[3] = NULL; + + fprintf(stderr, "Loading libwineserver...\n"); + + if(!(ws_lib_handle = wine_dlopen("libwineserver.so", RTLD_NOW, errmsg, sizeof(errmsg)))) + fatal_error("could not load wineserver lib: %s", errmsg); + + if(!(ws_start = wine_dlsym(ws_lib_handle, "wineserver_start_inproc", errmsg, sizeof(errmsg)))) + fatal_error("failed to find libwineserver entry point: %s", errmsg); + + call_req_handler_inproc = wine_dlsym(ws_lib_handle, "call_req_handler_inproc", errmsg, + sizeof(errmsg)); + if(!call_req_handler_inproc) + fatal_error("failed to find symbol call_req_handler_inproc in libwineserver: %s", + errmsg); + +#if 0 + fprintf(stderr, "sched_pol = %d, sched_max_pri = %d\n", sched_pol, sched_max_pri); + if(pthread_attr_init(&attr)) + fatal_perror("pthread_attr_init"); + + if(pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) + fatal_perror("pthread_attr_setinheritsched"); + + if(pthread_attr_setschedpolicy(&attr, sched_pol)) + fatal_perror("pthread_attr_setschedpolicy"); + + sched_param.sched_priority = sched_max_pri; + if(pthread_attr_setschedparam(&attr, &sched_param)) + fatal_perror("pthread_attr_setschedparam"); + + if(pthread_create(&ws_thread, &attr, ws_start, argv)) +#else + if(pthread_create(&ws_thread, NULL, ws_start, argv)) +#endif + fatal_perror("pthread_create failed"); + + /* FIXME: needs better mechanism to make sure wineserver started (this just waits 500 mS + * and if the thread doesn't terminate, presumes all is good). Or decide that no mechanism + * is needed. */ + if(!pthread_timedjoin_np(ws_thread, &wineserver_ret, &wait_time)) { + fatal_error("wineserver thread exited :("); + } + fprintf(stderr, "libwineserver loaded\n"); + } + started = 1; } @@ -1095,7 +1218,9 @@ size_t server_init_thread( void *entry_point ) req->wait_fd = ntdll_get_thread_data()->wait_fd[1]; req->debug_level = (TRACE_ON(server) != 0); req->cpu = client_cpu; - ret = wine_server_call( req ); + /* call_req_handler_inproc in server doesn't know how to handle this call properly, so it + * should always be called via the conventional call mechanism. */ + ret = wine_server_call_pipe( req ); NtCurrentTeb()->ClientId.UniqueProcess = ULongToHandle(reply->pid); NtCurrentTeb()->ClientId.UniqueThread = ULongToHandle(reply->tid); info_size = reply->info_size; diff --git a/dlls/ntdll/sync.c b/dlls/ntdll/sync.c index f759ce6..8cdded7 100644 --- a/dlls/ntdll/sync.c +++ b/dlls/ntdll/sync.c @@ -1113,7 +1113,7 @@ NTSTATUS NTDLL_wait_for_multiple_objects( UINT count, const HANDLE *handles, UIN req->timeout = abs_timeout; wine_server_add_data( req, &result, sizeof(result) ); wine_server_add_data( req, obj_handles, count * sizeof(*obj_handles) ); - ret = wine_server_call( req ); + ret = wine_server_call_pipe( req ); abs_timeout = reply->timeout; apc_handle = reply->apc_handle; call = reply->call; diff --git a/include/wine/server.h b/include/wine/server.h index d573d1f..de9a286 100644 --- a/include/wine/server.h +++ b/include/wine/server.h @@ -50,6 +50,7 @@ struct __server_request_info }; extern unsigned int wine_server_call( void *req_ptr ); +extern unsigned int wine_server_call_pipe( void *req_ptr ); extern void CDECL wine_server_send_fd( int fd ); extern int CDECL wine_server_fd_to_handle( int fd, unsigned int access, unsigned int attributes, HANDLE *handle ); extern int CDECL wine_server_handle_to_fd( HANDLE handle, unsigned int access, int *unix_fd, unsigned int *options ); diff --git a/server/Makefile.in b/server/Makefile.in index a2f1a52..03ceb79 100644 --- a/server/Makefile.in +++ b/server/Makefile.in @@ -1,5 +1,13 @@ DEFS = -D__WINESRC__ EXTRALIBS = @LIBPOLL@ +VERSION = 1.0 +SOVERSION = 1 +MODULE = libwineserver.$(LIBEXT) +SONAME = libwineserver.so.$(SOVERSION) +CFLAGS = @CFLAGS@ -fPIC + +SO_INSTALLDIRS = $(DESTDIR)$(libdir) + C_SRCS = \ async.c \ @@ -53,28 +61,48 @@ EXTRA_MANPAGES = wineserver.de.man wineserver.fr.man INSTALLDIRS = \ $(DESTDIR)$(bindir) \ + $(DESTDIR)$(libdir) \ $(DESTDIR)$(mandir)/man$(prog_manext) \ $(DESTDIR)$(mandir)/de.UTF-8/man$(prog_manext) \ $(DESTDIR)$(mandir)/fr.UTF-8/man$(prog_manext) -all: $(PROGRAMS) +all: $(PROGRAMS) $(MODULE) @MAKE_RULES@ wineserver: $(OBJS) $(CC) -o $@ $(OBJS) $(LIBWINE) $(LIBPORT) $(LDFLAGS) $(LIBS) $(LDRPATH_LOCAL) +libwineserver.so.$(VERSION): $(OBJS) $(VERSCRIPT) Makefile.in + $(LDSHARED) $(OBJS) $(EXTRALIBS) $(LDFLAGS) $(LIBS) -o $@ + +libwineserver.so.$(SOVERSION): libwineserver.so.$(VERSION) + $(RM) $@ && $(LN_S) libwineserver.so.$(VERSION) $@ + +libwineserver.so: libwineserver.so.$(SOVERSION) + $(RM) $@ && $(LN_S) libwineserver.so.$(SOVERSION) $@ + wineserver-installed: $(OBJS) $(CC) -o $@ $(OBJS) $(LIBWINE) $(LIBPORT) $(LDFLAGS) $(LIBS) $(LDRPATH_INSTALL) -install install-lib:: wineserver-installed $(DESTDIR)$(bindir) install-man-pages +install:: wineserver-installed $(DESTDIR)$(bindir) install-lib install-man-pages $(INSTALL_PROGRAM) wineserver-installed $(DESTDIR)$(bindir)/wineserver +install-lib:: libwineserver.so.$(VERSION) $(DESTDIR)$(libdir) + $(INSTALL_PROGRAM) libwineserver.so.$(VERSION) $(DESTDIR)$(libdir) + cd $(DESTDIR)$(libdir) \ + && $(RM) libwineserver.so libwineserver.so.$(SOVERSION) \ + && $(LN_S) libwineserver.so.$(VERSION) libwineserver.so.$(SOVERSION) \ + && $(LN_S) libwineserver.so.$(VERSION) libwineserver.so + install-man-pages:: $(EXTRA_MANPAGES) $(INSTALLDIRS) $(INSTALL_DATA) wineserver.de.man $(DESTDIR)$(mandir)/de.UTF-8/man$(prog_manext)/wineserver.$(prog_manext) $(INSTALL_DATA) wineserver.fr.man $(DESTDIR)$(mandir)/fr.UTF-8/man$(prog_manext)/wineserver.$(prog_manext) uninstall:: $(RM) $(DESTDIR)$(bindir)/wineserver + $(RM) $(DESTDIR)$(libdir)/libwineserver.so.$(VERSION) + $(RM) $(DESTDIR)$(libdir)/libwineserver.so.$(SOVERSION) + $(RM) $(DESTDIR)$(libdir)/libwineserver.so $(RM) $(DESTDIR)$(mandir)/de.UTF-8/man$(prog_manext)/wineserver.$(prog_manext) $(RM) $(DESTDIR)$(mandir)/fr.UTF-8/man$(prog_manext)/wineserver.$(prog_manext) diff --git a/server/fd.c b/server/fd.c index a8b3a5f..513197e 100644 --- a/server/fd.c +++ b/server/fd.c @@ -502,6 +502,19 @@ static inline void remove_epoll_user( struct fd *fd, int user ) } } +/* Obtain lock on wineserver_mutex, but only if we're running in-process */ +static void inline wineserver_lock() +{ + if(wineserver_is_inproc && pthread_mutex_lock(&wineserver_mutex)) + perror("wineserver: pthread_mutex_lock"); +} + +static void inline wineserver_unlock() +{ + if(wineserver_is_inproc && pthread_mutex_unlock(&wineserver_mutex)) + perror("wineserver: pthread_mutex_unlock"); +} + static inline void main_loop_epoll(void) { int i, ret, timeout; @@ -521,7 +534,9 @@ static inline void main_loop_epoll(void) if (!active_users) break; /* last user removed by a timeout */ if (epoll_fd == -1) break; /* an error occurred with epoll */ + wineserver_unlock(); ret = epoll_wait( epoll_fd, events, sizeof(events)/sizeof(events[0]), timeout ); + wineserver_lock(); set_current_time(); /* put the events into the pollfd array first, like poll does */ @@ -626,6 +641,7 @@ static inline void main_loop_epoll(void) if (!active_users) break; /* last user removed by a timeout */ if (kqueue_fd == -1) break; /* an error occurred with kqueue */ + wineserver_unlock(); if (timeout != -1) { struct timespec ts; @@ -635,6 +651,7 @@ static inline void main_loop_epoll(void) ret = kevent( kqueue_fd, NULL, 0, events, sizeof(events)/sizeof(events[0]), &ts ); } else ret = kevent( kqueue_fd, NULL, 0, events, sizeof(events)/sizeof(events[0]), NULL ); + wineserver_lock(); set_current_time(); @@ -730,6 +747,7 @@ static inline void main_loop_epoll(void) if (!active_users) break; /* last user removed by a timeout */ if (port_fd == -1) break; /* an error occurred with event completion */ + wineserver_unlock(); if (timeout != -1) { struct timespec ts; @@ -739,6 +757,7 @@ static inline void main_loop_epoll(void) ret = port_getn( port_fd, events, sizeof(events)/sizeof(events[0]), &nget, &ts ); } else ret = port_getn( port_fd, events, sizeof(events)/sizeof(events[0]), &nget, NULL ); + wineserver_lock(); if (ret == -1) break; /* an error occurred with event completion */ @@ -889,7 +908,9 @@ void main_loop(void) if (!active_users) break; /* last user removed by a timeout */ + wineserver_unlock(); ret = poll( pollfd, nb_users, timeout ); + wineserver_lock(); set_current_time(); if (ret > 0) diff --git a/server/main.c b/server/main.c index 2d841e8..72d92e0 100644 --- a/server/main.c +++ b/server/main.c @@ -41,6 +41,7 @@ /* command-line options */ int debug_level = 0; int foreground = 0; +int wineserver_is_inproc = 0; timeout_t master_socket_timeout = 3 * -TICKS_PER_SEC; /* master socket timeout, default is 3 seconds */ const char *server_argv0; @@ -125,7 +126,7 @@ static void sigterm_handler( int signum ) exit(1); /* make sure atexit functions get called */ } -int main( int argc, char *argv[] ) +static int wineserver_start( int argc, char *argv[] ) { setvbuf( stderr, NULL, _IOLBF, 0 ); parse_args( argc, argv ); @@ -148,3 +149,17 @@ int main( int argc, char *argv[] ) main_loop(); return 0; } + +int main( int argc, char *argv[] ) +{ + return wineserver_start(argc, argv); +} + +void *wineserver_start_inproc(char *argv[]) +{ + int argc = 0; + while (argv[argc]) ++argc; + wineserver_is_inproc = 1; + wineserver_start(argc, argv); + return 0; +} diff --git a/server/object.h b/server/object.h index a8cb327..51de21a 100644 --- a/server/object.h +++ b/server/object.h @@ -25,10 +25,21 @@ #include #endif +#ifdef HAVE_PTHREAD_H +#include +#else +/* FIXME */ +#endif + #include #include "wine/server_protocol.h" #include "wine/list.h" +/* FIXME: need more portable locking mechanism */ + +extern pthread_mutex_t wineserver_mutex; /* Synchronize access to wineserver data between threads */ +extern int wineserver_is_inproc; + #define DEBUG_OBJECTS /* kernel objects */ diff --git a/server/request.c b/server/request.c index 05c7464..4c511ec 100644 --- a/server/request.c +++ b/server/request.c @@ -51,6 +51,7 @@ #ifdef HAVE_POLL_H #include #endif +#include #include "ntstatus.h" #define WIN32_NO_STATUS @@ -64,6 +65,7 @@ #include "process.h" #define WANT_REQUEST_HANDLERS #include "request.h" +#include "wine/server.h" /* Some versions of glibc don't define this */ #ifndef SCM_RIGHTS @@ -126,6 +128,8 @@ int config_dir_fd = -1; /* file descriptor for the config dir */ static struct master_socket *master_socket; /* the master socket object */ static struct timeout_user *master_timeout; +pthread_mutex_t wineserver_mutex; /* keep calls from in-process wine app serialized with out-of-process calls */ + /* complain about a protocol error and terminate the client connection */ void fatal_protocol_error( struct thread *thread, const char *err, ... ) { @@ -254,6 +258,79 @@ static void send_reply( union generic_reply *reply ) fatal_protocol_perror( current, "reply write" ); } +void call_req_handler_inproc( void * req_ptr ) { + struct __server_request_info *req = (struct __server_request_info *)req_ptr; + union generic_reply reply; + struct thread *thread; + enum request reqnum; + size_t max_reply_size = req->u.req.request_header.reply_size; + char *p; + size_t i; + + if(pthread_mutex_lock(&wineserver_mutex)) + fatal_perror("call_req_handler_inproc: pthread_mutex_lock"); + + thread = get_thread_from_id(GetCurrentThreadId()); + if(!thread) thread = get_thread_from_pid(getpid()); + if(!thread) fatal_perror("call_req_handler_inproc: failed to get thread object"); + + grab_object(thread); + + memcpy(&(thread->req), req, sizeof(thread->req)); + p = (char *)malloc(req->u.req.request_header.request_size); + if(!p) fatal_perror("malloc"); + thread->req_data = p; + for(i = 0; i < req->data_count; ++i) { + memcpy(p, req->data[i].ptr, req->data[i].size); + p += req->data[i].size; + } + + reqnum = thread->req.request_header.req; + + current = thread; + current->reply_size = 0; + clear_error(); + memset( &reply, 0, sizeof(reply) ); + + if (debug_level) trace_request(); + + if (reqnum < REQ_NB_REQUESTS) + req_handlers[reqnum]( ¤t->req, &reply ); + else + set_error( STATUS_NOT_IMPLEMENTED ); + + /* copy back the reply over the request union/struct (but not the whole __server_request_info) */ + + free (thread->req_data); + thread->req_data = NULL; + + if(current) { + memcpy(req, &reply, sizeof(reply)); + req->u.reply.reply_header.error = current->error; + req->u.reply.reply_header.reply_size = current->reply_size; + + if (debug_level) trace_reply( reqnum, &req->u.reply ); + + if(req->reply_data) { + /* FIXME: clean this up */ + if(current->reply_size > max_reply_size) { + fatal_error("reply buffer too small, supplied %u bytes, requires %u\n", + max_reply_size, current->reply_size); + } + memcpy(req->reply_data, current->reply_data, current->reply_size); + free(current->reply_data); + } + + current->reply_data = NULL; + current = NULL; + } + + release_object( thread ); + + if(pthread_mutex_unlock(&wineserver_mutex)) + fatal_perror("call_req_handler_inproc: pthread_mutex_unlock"); +} + /* call a request handler */ static void call_req_handler( struct thread *thread ) { @@ -758,6 +835,11 @@ void open_master_socket(void) assert( sizeof(union generic_request) == sizeof(struct request_max_size) ); assert( sizeof(union generic_reply) == sizeof(struct request_max_size) ); + /* init mutex for serializing in-process and out-of-process requests */ + if(pthread_mutex_init(&wineserver_mutex, NULL)) { + fatal_perror("pthread_mutex_init failed"); + } + /* make sure the stdio fds are open */ fd = open( "/dev/null", O_RDWR ); while (fd >= 0 && fd <= 2) fd = dup( fd ); @@ -840,5 +922,10 @@ void close_master_socket( timeout_t timeout ) if (master_timeout) /* cancel previous timeout */ remove_timeout_user( master_timeout ); + if(pthread_mutex_destroy(&wineserver_mutex)) { + perror("pthread_mutex_destroy"); + } + memset(&wineserver_mutex, 0, sizeof(wineserver_mutex)); + master_timeout = add_timeout_user( timeout, close_socket_timeout, NULL ); } --------------1.7.3.4--