Patchwork [1/7] Add vectorized getenv for glibc use

login
register
mail settings
Submitter Andi Kleen
Date Dec. 19, 2014, 7:37 a.m.
Message ID <1418974667-32587-2-git-send-email-andi@firstfloor.org>
Download mbox | patch
Permalink /patch/4358/
State New
Headers show

Comments

Andi Kleen - Dec. 19, 2014, 7:37 a.m.
From: Andi Kleen <ak@linux.intel.com>

This adds a general "vectorized getenv" for glibc internal use.
The motivation is to allow subsystems to access environment variables
cheaply without having to rescan the environment completely.

The dynamic linker already walks the environment to look for its
LD_* variables. Extend this code to look for a number of
pre-registered GLIBC_* variables too. This can be done at basically
no additional cost. The only two variables currently pre-registered
are for the lock elision code.

For static builds which do not use the dynamic linker a similar
environment walking function is called at init time.

The variable values are saved in a global array that can be directly
accessed by libc and related libraries like libpthread.

2014-12-17  Andi Kleen  <ak@linux.intel.com>

	* elf/Makefile (dl-glibc-var.o): Add new file.
	* elf/Versions (_dl_glibc_var, __glibc_var_init): Export new symbols
	as GLIBC_PRIVATE.
	* elf/dl-environ.c (_dl_next_ld_env_entry): Look for GLIBC_ prefixes
	too. Return first character in new argument.
	* elf/dl-glibc-var.c (struct glibc_var): Define global array.
	(__record_glibc_var): New function to save glibc variables.
	(next_env_entry): Function to walk environment.
	(__glibc_var_init): Fallback function for static builds.
	* elf/rtld.c: Update comment.
	(process_envvars): Handle GLIBC_ variables and call __record_glibc_var.
	* include/glibc-var.h: New file.
	* sysdeps/generic/ldsodefs.h (_dl_next_ld_env_entry): Update prototype.
	* .gitignore: Add exception for glibc-var.h.
---
 .gitignore                 |   1 +
 elf/Makefile               |   1 +
 elf/Versions               |   2 +
 elf/dl-environ.c           |  19 +++++++-
 elf/dl-glibc-var.c         | 110 +++++++++++++++++++++++++++++++++++++++++++++
 elf/rtld.c                 |  17 ++++++-
 include/glibc-var.h        |  49 ++++++++++++++++++++
 sysdeps/generic/ldsodefs.h |   6 +--
 8 files changed, 198 insertions(+), 7 deletions(-)
 create mode 100644 elf/dl-glibc-var.c
 create mode 100644 include/glibc-var.h

Patch

diff --git a/.gitignore b/.gitignore
index 93c2e54..042a1d8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,7 @@  AUTHORS
 copyr-*
 copying.*
 glibc-*
+!glibc-var.h
 
 configparms
 
diff --git a/elf/Makefile b/elf/Makefile
index 9e07073..1811ac5 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -35,6 +35,7 @@  dl-routines	= $(addprefix dl-,load lookup object reloc deps hwcaps \
 ifeq (yes,$(use-ldconfig))
 dl-routines += dl-cache
 endif
+dl-routines += dl-glibc-var
 all-dl-routines = $(dl-routines) $(sysdep-dl-routines)
 # But they are absent from the shared libc, because that code is in ld.so.
 elide-routines.os = $(all-dl-routines) dl-support enbl-secure dl-origin \
diff --git a/elf/Versions b/elf/Versions
index 23deda9..3d34646 100644
--- a/elf/Versions
+++ b/elf/Versions
@@ -52,9 +52,11 @@  ld {
   }
   GLIBC_PRIVATE {
     # Those are in the dynamic linker, but used by libc.so.
+    __glibc_var_init;
     __libc_enable_secure;
     _dl_allocate_tls; _dl_allocate_tls_init;
     _dl_argv; _dl_find_dso_for_object; _dl_get_tls_static_info;
+    _dl_glibc_var;
     _dl_deallocate_tls; _dl_make_stack_executable; _dl_out_of_memory;
     _dl_rtld_di_serinfo; _dl_starting_up; _dl_tls_setup;
     _rtld_global; _rtld_global_ro;
diff --git a/elf/dl-environ.c b/elf/dl-environ.c
index afcd11d..dc907d8 100644
--- a/elf/dl-environ.c
+++ b/elf/dl-environ.c
@@ -22,10 +22,10 @@ 
 #include <ldsodefs.h>
 
 /* Walk through the environment of the process and return all entries
-   starting with `LD_'.  */
+   starting with `LD_' or 'GLIBC_'.  */
 char *
 internal_function
-_dl_next_ld_env_entry (char ***position)
+_dl_next_ld_env_entry (char ***position, char *first)
 {
   char **current = *position;
   char *result = NULL;
@@ -35,6 +35,7 @@  _dl_next_ld_env_entry (char ***position)
       if (__builtin_expect ((*current)[0] == 'L', 0)
 	  && (*current)[1] == 'D' && (*current)[2] == '_')
 	{
+	  *first = (*current)[0];
 	  result = &(*current)[3];
 
 	  /* Save current position for next visit.  */
@@ -43,6 +44,20 @@  _dl_next_ld_env_entry (char ***position)
 	  break;
 	}
 
+      if (__builtin_expect ((*current)[0] == 'G', 0)
+	  && (*current)[1] == 'L' && (*current)[2] == 'I'
+	  && (*current)[3] == 'B' && (*current)[4] == 'C'
+	  && (*current)[5] == '_')
+	{
+	  *first = (*current)[0];
+	  result = &(*current)[6];
+
+	  /* Save current position for next visit.  */
+	  *position = ++current;
+
+	  break;
+	}
+
       ++current;
     }
 
diff --git a/elf/dl-glibc-var.c b/elf/dl-glibc-var.c
new file mode 100644
index 0000000..2a19bea
--- /dev/null
+++ b/elf/dl-glibc-var.c
@@ -0,0 +1,110 @@ 
+/* Fast access to GLIBC_* environment variables, without having to walk
+   the environment multiple times.
+   Copyright (C) 2013 Free Software Foundation, Inc.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <string.h>
+#include <glibc-var.h>
+
+#define _GLIBC_VAR_ENTRY(a) [GLIBC_VAR_##a] = { #a, sizeof(#a) - 1, NULL }
+
+struct glibc_var _dl_glibc_var[] =
+{
+  _GLIBC_VAR_ENTRY(PTHREAD_MUTEX),
+  _GLIBC_VAR_ENTRY(PTHREAD_RWLOCK),
+  /* Add more GLIBC_ variables here.  */
+  /* Make the names as long as possible to pass code review.  */
+  [GLIBC_VAR_MAX] =    { NULL, 0, NULL }
+};
+
+internal_function void
+__record_glibc_var (char *name, int len, char *val)
+{
+  int i;
+
+  for (i = 0; i < GLIBC_VAR_MAX; i++)
+    {
+      struct glibc_var *v = &_dl_glibc_var[i];
+
+      if (len == v->len && memcmp (v->name, name, v->len) == 0)
+	{
+	  v->val = val;
+	  break;
+	}
+    }
+  /* Ignore unknown GLIBC_ variables.  */
+}
+
+#ifndef SHARED
+
+/* If SHARED the env walk is shared with rtld.c.  */
+
+static char *
+next_env_entry (char first, char ***position)
+{
+  char **current = *position;
+  char *result = NULL;
+
+  while (*current != NULL)
+    {
+      if ((*current)[0] == first)
+	{
+	  result = *current;
+	  *position = ++current;
+	  break;
+	}
+
+      ++current;
+    }
+
+  return result;
+}
+
+/* May be called from libpthread.  */
+
+void
+__glibc_var_init (int argc __attribute__ ((unused)),
+		  char **argv  __attribute__ ((unused)),
+		  char **environ)
+{
+  char *envline;
+  static int initialized;
+
+  if (initialized != 0)
+    return;
+  initialized = 1;
+
+  while ((envline = next_env_entry ('G', &environ)) != NULL)
+    {
+      if (envline[1] == 'L' && envline[2] == 'I' && envline[3] == 'B'
+	  && envline[4] == 'C' && envline[5] == '_')
+	{
+	  char *e = envline + 6;
+	  while (*e && *e != '=')
+	    e++;
+	  if (*e == 0)
+	    continue;
+	  __record_glibc_var (envline + 6, e - (envline + 6), e + 1);
+	}
+    }
+}
+
+void (*const __glibc_var_init_array []) (int, char **, char **)
+  __attribute__ ((section (".preinit_array"), aligned (sizeof (void *)))) =
+{
+  &__glibc_var_init
+};
+#endif
diff --git a/elf/rtld.c b/elf/rtld.c
index fb02ecd..a5c1aa2 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -41,6 +41,7 @@ 
 #include <tls.h>
 #include <stap-probe.h>
 #include <stackinfo.h>
+#include <glibc-var.h>
 
 #include <assert.h>
 
@@ -2338,7 +2339,9 @@  process_dl_audit (char *str)
 
 /* Process all environments variables the dynamic linker must recognize.
    Since all of them start with `LD_' we are a bit smarter while finding
-   all the entries.  */
+   all the entries.  In addition we also save a bunch of GLIBC_ variables
+   used by other parts of glibc, so that each startup only has to walk the
+   environment once.  */
 extern char **_environ attribute_hidden;
 
 
@@ -2349,12 +2352,13 @@  process_envvars (enum mode *modep)
   char *envline;
   enum mode mode = normal;
   char *debug_output = NULL;
+  char first;
 
   /* This is the default place for profiling data file.  */
   GLRO(dl_profile_output)
     = &"/var/tmp\0/var/profile"[__libc_enable_secure ? 9 : 0];
 
-  while ((envline = _dl_next_ld_env_entry (&runp)) != NULL)
+  while ((envline = _dl_next_ld_env_entry (&runp, &first)) != NULL)
     {
       size_t len = 0;
 
@@ -2367,6 +2371,15 @@  process_envvars (enum mode *modep)
 	   invalid memory below.  */
 	continue;
 
+      /* Must be for GLIBC_ */
+      if (first == 'G')
+	{
+	  __record_glibc_var (envline, len, envline + len + 1);
+	  continue;
+	}
+
+      /* Must be for LD_ */
+
       switch (len)
 	{
 	case 4:
diff --git a/include/glibc-var.h b/include/glibc-var.h
new file mode 100644
index 0000000..c9b2f21
--- /dev/null
+++ b/include/glibc-var.h
@@ -0,0 +1,49 @@ 
+/* Fast access to GLIBC_* environment variables, without having to walk
+   the environment. Register new ones in in elf/glibc-var.c
+   Copyright (C) 2013 Free Software Foundation, Inc.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#ifndef _GLIBC_VAR_H
+# define _GLIBC_VAR_H 1
+
+# include <libc-symbols.h>
+
+enum
+{
+ GLIBC_VAR_PTHREAD_MUTEX,
+ GLIBC_VAR_PTHREAD_RWLOCK,
+ GLIBC_VAR_MAX
+};
+
+struct glibc_var
+{
+  const char *name;
+  int len;
+  char *val;
+};
+
+extern struct glibc_var _dl_glibc_var[];
+extern void __record_glibc_var (char *name, int len, char *val) internal_function;
+
+/* Call this if you're in a constructor that may run before glibc-var's.  */
+# ifndef SHARED
+extern void __glibc_var_init (int ac, char **av, char **env);
+# else
+/* For shared this is always done in the dynamic linker early enough.  */
+# define __glibc_var_init(a,b,c) do {} while (0)
+# endif
+
+#endif
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index d07eaac..b8bd98c 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -893,9 +893,9 @@  extern void _dl_mcount_wrapper (void *selfpc);
 /* Show the members of the auxiliary array passed up from the kernel.  */
 extern void _dl_show_auxv (void) internal_function;
 
-/* Return all environment variables starting with `LD_', one after the
-   other.  */
-extern char *_dl_next_ld_env_entry (char ***position) internal_function;
+/* Return all environment variables starting with `LD_' or `GLIBC_', one
+   after the other.  */
+extern char *_dl_next_ld_env_entry (char ***position, char *first) internal_function;
 
 /* Return an array with the names of the important hardware capabilities.  */
 extern const struct r_strlenpair *_dl_important_hwcaps (const char *platform,