Sysenter/sysexit VS int $0x80

Da LugMan TNG.

In un sistema Linux x86 (quindi a 32bit) le chiamate di sistema vengono fatte storicamente avvalendosi dell'istruzione INT 80h. Questo interrupt è un interrupt di tipo software e viene "programmato" in fase di inizializzazione del kernel, subito dopo il boot. L'INT 80h non fa altro che portare il processo che esegue la chiamata dallo spazio utente (il processo appunto) allo spazio kernel. Dai processori Pentium2 sono però state introdotte altre due istruzioni per effettuare in modo più ottimizzato questo salto. Queste istruzioni sono Sysenter e Sysexit che rispettivamente permettono di "entrare ed uscire" dal kernel.

Per poter sfruttare queste istruzioni occorre che il processore, il kernel e le glibc le supportino! In particolare si deva avere che:

  • Il processore sia un Penium2 o superiore
  • Il kernel sia un 2.6.x, ma il supporto fu introdotto dalla versione sperimentale 2.5.53
  • La glibc supporti queste istruzioni e, soprattutto, sia stata compilata adeguatamente per introdurre questa feature

Benchmark: quando e perchè usare Sysenter

L'istruzione Sysenter permette di ottimizzare accelerando l'ingresso nel kernel e quindi ottimizzare tutte le chiamate di siatema. Si è notato infatti che le chiamate eseguite in INT 80h sono più lente rispetto alla SYSENTER e un Pentium4 impiega di più di un Pentium3 (e qualche volta di un Pentum2) per eseguire certe operazioni di sistema. In questi casi quindi è bene usare questa chiamata. Ciò che segue prende in riferimento una Slackware 13 con kernel 2.6.32.2 su P4 e glibc 2.9.

Test

Per eseguire un test comparativo, basta un qualsiasi sistema Linux 2.6 e non è necessario che le librerie glibc supportino di già la sysenter, come vedremo tra poco. Il sistema Linux di riferimento è, come detto, una Slackware. La slackware ottimizza le librerie per i486 senza abilitare l'istruzione SYSENTER. Quindi per poter testare la velocità occorre scrivere del codice assembly. Il benchmark proposto imita nella sostanza quello dato all'indirizzo http://kerneltrap.org/node/531, ossia vogliamo eseguire 20 milioni di chiamate getpid(). Il codice proposto è il seguente:

#include <unistd.h>
//numero di cicli da fare
#define K 1000000

#ifdef NO_DYNAMIC_VDSO
#define sys_getpid() __asm__( \
                                  "movl $20, %eax    \n" \
                                  "call 0xffffe414   \n" \
                                  "movl %eax, pid    \n" \
                                  );
#else
#define sys_getpid() __asm__( \
                                 "movl $20, %eax    \n" \
                                 "call *%gs:0x10    \n" \
                                 "movl %eax, pid    \n" \
                                 );
#endif

int pid=0;
unsigned long i;

int main(int c, char **v)
{
   if(c>1) // se passo un parametro qualsiasi, uso le chiamate di sistema
     {  
	puts("GetPid()");
	asm("xor %ebx, %ebx"); 	// mi assicuro di azzerare un registro che non viene usato
	while(i++ < K) { 	// eseguirò 20 chiamate per un totale di K*20
	   pid = getpid(); asm("mov %ebx, %gs:0x48;"); //azzero il valore del pid in memoria
	   pid = getpid(); asm("mov %ebx, %gs:0x48;");
	   pid = getpid(); asm("mov %ebx, %gs:0x48;");	
	   pid = getpid(); asm("mov %ebx, %gs:0x48;");
	   pid = getpid(); asm("mov %ebx, %gs:0x48;");
	   pid = getpid(); asm("mov %ebx, %gs:0x48;");
	   pid = getpid(); asm("mov %ebx, %gs:0x48;");
	   pid = getpid(); asm("mov %ebx, %gs:0x48;");
	   pid = getpid(); asm("mov %ebx, %gs:0x48;");
	   pid = getpid(); asm("mov %ebx, %gs:0x48;");
	   pid = getpid(); asm("mov %ebx, %gs:0x48;");
	   pid = getpid(); asm("mov %ebx, %gs:0x48;");
	   pid = getpid(); asm("mov %ebx, %gs:0x48;");
	   pid = getpid(); asm("mov %ebx, %gs:0x48;");
	   pid = getpid(); asm("mov %ebx, %gs:0x48;");
	   pid = getpid(); asm("mov %ebx, %gs:0x48;");
	   pid = getpid(); asm("mov %ebx, %gs:0x48;");
	   pid = getpid(); asm("mov %ebx, %gs:0x48;");
	   pid = getpid(); asm("mov %ebx, %gs:0x48;");
	   pid = getpid(); asm("mov %ebx, %gs:0x48;");
	}
     } else  // altrimenti senza parametri uso la SYSENTER
     {
	puts("__asm__ ");
	while(i++ < K)
	  {
	     sys_getpid();
	     sys_getpid();
	     sys_getpid();
	     sys_getpid();
	     sys_getpid();
	     sys_getpid();
	     sys_getpid();
	     sys_getpid();
	     sys_getpid();
	     sys_getpid();
	     sys_getpid();
	     sys_getpid();
	     sys_getpid();
	     sys_getpid();
	     sys_getpid();
	     sys_getpid();
	     sys_getpid();
	     sys_getpid();
	     sys_getpid();
	     sys_getpid();
	  }
     }
   
   return pid;
}

Questo codice NON va compilato staticamente con l'opzione -DNO_DYNAMIC_VDSO se il VDSO è dinamico, altrimenti sys_getpid() genererà un SEGFAULT. Per compilarlo occorre valutare due condizioni:

  1. VDSO statico: se eseguendo il comando cat /proc/sys/vm/vdso_enabled il risulatato è 1, il vostro VDSO è dinamico. Lo si può anche vedere eseguento ldd /bin/ls due volte e vedere che la libreria (virtuale) linux-gate.so cambia il proprio indirizzo. In questo caso la compilazione si effettua semplicemente con make misura_getpid.
  2. VDSO dinamico: se eseguendo il comando cat /proc/sys/vm/vdso_enabled il risulatato è 2, il vostro VDSO è statico. Lo si può anche vedere eseguento ldd /bin/ls due volte e vedere che la libreria (virtuale) linux-gate.so non cambia il proprio indirizzo. In questo caso si può compilare anche con gcc -DNO_DINAMIC_VDSO misura_getpid.c -o misura_getpid.

Attenzione: se cat /proc/sys/vm/vdso_enabled fosse 0, NON potreste usare ne SYSCALL ne SYSENTER. O quantomeno il sistema dovrebbe puntare ad un indirizzo in cui verrebbe eseguito INT 80h, perdendo di fatto ogni beneficio.

Eseguiamo quindi il codice compilato misurando il tempo. Lanciamo da shell l'eseguibile:

time ./misura_getpid c ; time ./misura_getpid

Sul mio sistema, con VDSO forzato statico, il risultato è il seguente:

GetPid()

real    0m9.613s
user    0m4.400s
sys     0m5.192s
__asm__ 

real    0m3.381s
user    0m1.228s
sys     0m2.132s

Come si nota i tempi di esecuzione sono ridotti circa a 1/3 usando la SYSENTER!! Quindi non c'è motivo di non usarla.

Note sul codice
  1. Cosa significano "call *%gs:0x10" e "call 0xffffe414"? queste chiamate dicono al processo di saltare all'indirizzo __kernel_vsyscall. Questo indirizzo eseguirà o la SYSENTER se supportata, o l'INT 80h.
  2. Ma perchè proprio "call *%gs:0x10" e "call 0xffffe414"? questi indirizzi possono variare con la versione del kernel. Per esempio poco tempo fa l'indirizzo __kernel_vsyscall era a 0xfffe400. Nei link sotto riportati viene spiegato come determinare questi indirizzi.
  3. Perchè asm("xor %ebx, %ebx"); prima delle chiamate di sistema? perchè devo creare un registro azzerato per poter azzerare l'indirizzio di memoria "%gs:0x48". Il registro EBX non viene mai toccato e quindi ho scelto quello.
  4. E' proprio necessario azzerare "%gs:0x48"? Sì. Gli esempi riportati in rete NON mostrano il codice dei loro benchmark, ma dicono che usano la chiamata getpid(). Questa chiamata però è ingannevole e quanto sto per dire è stato verificato per glibc-2.9. Se provassimo a disassemblare getpid() da un binario compilato staticamente si otterrebbe una cosa come questa:
0x08051a10 <getpid+0>:  mov    %gs:0x4c,%edx
0x08051a17 <getpid+7>:  cmp    $0x0,%edx
0x08051a1a <getpid+10>: mov    %edx,%eax
0x08051a1c <getpid+12>: jle    0x8051a20 <getpid+16>
0x08051a1e <getpid+14>: repz ret 
0x08051a20 <getpid+16>: jne    0x8051a2c <getpid+28>
0x08051a22 <getpid+18>: mov    %gs:0x48,%eax
0x08051a28 <getpid+24>: test   %eax,%eax
0x08051a2a <getpid+26>: jne    0x8051a1e <getpid+14>
0x08051a2c <getpid+28>: mov    $0x14,%eax
0x08051a31 <getpid+33>: int    $0x80
0x08051a33 <getpid+35>: test   %edx,%edx
0x08051a35 <getpid+37>: mov    %eax,%ecx
0x08051a37 <getpid+39>: jne    0x8051a1e <getpid+14>
0x08051a39 <getpid+41>: mov    %ecx,%gs:0x48
0x08051a40 <getpid+48>: ret 
In pratica il processo chiede al kernel qual è il suo PID e lo salva subito in "%gs:0x48" (con l'istruzione "mov %ecx,%gs:0x48"). Quindi alla seconda chiamata, il sistema caricherà il PID in EAX con l'istruzione "mov %gs:0x48,%eax" (quindi non effettua nessuna call) e valuta se è diverso da 0 ("test %eax,%eax"). Se non lo è allora il valore è ritenuto il PID del processo e viene restituito senza effettuare l'INT 80h rendendo tutto estremamente veloce, ma falsando i risultati del test.

SlackWare - Abilitazione delle SYSENTER/SYSEXIT

Come detto Slackware 13 e inferiori NON usano le istruzioni SYSENTER. Ma cosa occorre fre per abilitarle? Semplice: l'uso di SYSENTER/SYSEXIT dipende in ultima analisi principalmente dalle [librerie di sistema glibc! Basta semplicemente ricompilarle, ma con le debite migliorie. Di seguito è riportato lo slackbuild utilizzato per fare ciò. Questo è lo slackbuild che si trova nei repository SlackWare 13 opportunamente modificato per i686:

#!/bin/sh
# Copyright 2006, 2008, 2009  Patrick J. Volkerding, Sebeka, MN, USA
# All rights reserved.
## build glibc-$VERSION for Slackware

VERSION=${VERSION:-2.9}
CHECKOUT=${CHECKOUT:--20090316}
BUILD=${BUILD:-calzo}
# $ARCH may be preset, otherwise i486 compatibility with i686 binary
# structuring is the Slackware default, since this is what gcc-3.2+
# requires for binary compatibility with previous releases.
ARCH=i686

CVSVER=${VERSION}${CHECKOUT}

# NOTE!!!  glibc needs to be built against the sanitized kernel headers,
# which will be installed under /usr/include by the kernel-headers package.
# Be sure the correct version of the headers package is installed BEFORE
# building glibc!

CWD=$(pwd)
# Temporary build location.  This should not be a directory
# path a non-root user could create later...
TMP=${TMP:-/glibc-tmp-$(mcookie)}
mkdir -p $TMP

# Sanity check on the version number in the install scripts:
if ! grep -vq libutil-$VERSION}.so $CWD/doinst.sh-glibc ; then
  echo "FATAL:  doinst.sh scripts have wrong version numbers."
  exit 1
fi

case $ARCH in
  i686)
    echo
    echo ====================== i686 ======================
    echo
    sleep 2;
    TARGET=pentium4
    ;;
  x86_64)
    TARGET=${TARGET:-x86_64}
    ;;
  i486)
    # This should be i486 for all 32-bit x86 arch:
    TARGET=${TARGET:-i486}
    ;;
esac

# This function fixes a doinst.sh file for x86_64.
# With thanks to Fred Emmott.
fix_doinst() {
  if [ "x$LIBDIRSUFFIX" = "x" ]; then
    return;
  fi;
  # Fix "( cd usr/lib ;" occurrences
  sed -i "s#lib ;#lib${LIBDIRSUFFIX} ;#" install/doinst.sh
  # Fix "lib/" occurrences
  sed -i "s#lib/#lib${LIBDIRSUFFIX}/#g" install/doinst.sh
  # Fix "( cd lib" occurrences
  sed -i "s#( cd lib\$#( cd lib${LIBDIRSUFFIX}#" install/doinst.sh

  if [ "$ARCH" = "x86_64" ]; then
    sed -i 's#ld-linux.so.2#ld-linux-x86-64.so.2#' install/doinst.sh
  fi
}

# This is a patch function to put all glibc patches in the build script
# up near the top.
apply_patches() {
  # Use old-style locale directories rather than a single (and strangely
  # formatted) /usr/lib/locale/locale-archive file:
  zcat $CWD/glibc.locale.no-archive.diff.gz | patch -p1 --verbose || exit 1
  # The is_IS locale is causing a strange error about the "echn" command
  # not existing.  This patch reverts is_IS to the version shipped in
  # glibc-2.5:
  zcat $CWD/is_IS.diff.gz | patch -p1 --verbose || exit 1
  # Fix NIS netgroups:
  zcat $CWD/glibc.nis-netgroups.diff.gz | patch -p1 --verbose || exit 1
  # Support ru_RU.CP1251 locale:
  zcat $CWD/glibc.ru_RU.CP1251.diff.gz | patch -p1 --verbose || exit 1
  # Fix missing MAX macro in getcwd.c:
  zcat $CWD/glibc.getcwd.max.macro.diff.gz | patch -p1 --verbose || exit 1
  # Fix resolver problem with glibc-2.9:
  zcat $CWD/glibc-2.10-dns-no-gethostbyname4.diff.gz | patch -p0 --verbose || exit 1
  # This reverts a patch that was made to glibc to fix "namespace leakage",
  # which seems to cause some build failures (e.g. with conntrack):
  zcat $CWD/glibc.revert.to.fix.build.breakages.diff.gz | patch -p1 --verbose || exit 1
  # Update the timezone information.
  ( cd timezone
    tar xzf $CWD/tzdata2009d.tar.gz
    chown root:root *
    mv yearistype.sh yearistype
    chmod 644 *
    chmod 755 yearistype
    mkdir tzcode
    cd tzcode
    tar xzf $CWD/tzcode2009d.tar.gz
    chown -R root:root .
    chmod 644 *
    cp -a *.c *.h ..
  )
}

# I'll break this out as an option for fun  :-)
case $ARCH  in
  i386)
    OPTIMIZ="-O3 -march=i386 -mcpu=i686"
    LIBDIRSUFFIX=""
    ;;
  i486)
    OPTIMIZ="-O3 -march=i486 -mtune=i686"
    LIBDIRSUFFIX=""
    ;;
  i586)
    OPTIMIZ="-O3 -march=i586"
    LIBDIRSUFFIX=""
    ;;
  i686)
    echo ====================== ADD Flags ======================
    sleep 2;
    OPTIMIZ="-O3 -march=i686 -mtune=i686"
    LIBDIRSUFFIX=""
    ;;
  athlon)
    OPTIMIZ="-O3 -march=athlon"
    LIBDIRSUFFIX=""
    ;;
  s390)
    OPTIMIZ="-O3"
    LIBDIRSUFFIX=""
    ;;
  x86_64)
    OPTIMIZ="-O3 -fPIC"
    LIBDIRSUFFIX="64"
    ;;
  *)
    OPTIMIZ="-O3"
    LIBDIRSUFFIX=""
    ;;
esac

# This is going to be the initial $DESTDIR:
export PKG=$TMP/package-glibc-incoming-tree
PGLIBC=$TMP/package-glibc
PSOLIBS=$TMP/package-glibc-solibs
PZONE=$TMP/package-glibc-zoneinfo
PI18N=$TMP/package-glibc-i18n
PPROFILE=$TMP/package-glibc-profile
PDEBUG=$TMP/package-glibc-debug

# Empty these locations first:
for dir in $PKG $PGLIBC $PSOLIBS $PZONE $PI18N $PPROFILE $PDEBUG ; do
  if [ -d $dir ]; then
    rm -rf $dir
  fi
  mkdir -p $dir
done
if [ -d $TMP/glibc-$VERSION ]; then
  rm -rf $TMP/glibc-$VERSION
fi

# Create an incoming directory structure for glibc to be built into:
mkdir -p $PKG/lib${LIBDIRSUFFIX}
mkdir -p $PKG/sbin
mkdir -p $PKG/usr/bin
mkdir -p $PKG/usr/lib${LIBDIRSUFFIX}
mkdir -p $PKG/usr/sbin
mkdir -p $PKG/usr/include
mkdir -p $PKG/usr/doc
mkdir -p $PKG/usr/man
mkdir -p $PKG/usr/share
mkdir -p $PKG/var/db/nscd
mkdir -p $PKG/var/run/nscd

# Begin extract/compile:
cd $TMP
rm -rf glibc-$CVSVER
tar xjf $CWD/glibc-$CVSVER.tar.bz2
cd glibc-$CVSVER

chown -R root:root .
find . -perm 666 -exec chmod 644 {} \;
find . -perm 664 -exec chmod 644 {} \;
find . -perm 600 -exec chmod 644 {} \;
find . -perm 444 -exec chmod 644 {} \;
find . -perm 400 -exec chmod 644 {} \;
find . -perm 440 -exec chmod 644 {} \;
find . -perm 777 -exec chmod 755 {} \;
find . -perm 775 -exec chmod 755 {} \;
find . -perm 511 -exec chmod 755 {} \;
find . -perm 711 -exec chmod 755 {} \;
find . -perm 555 -exec chmod 755 {} \;

# Clean up leftover CVS directories:
find . -type d -name CVS -exec rm -r {} \; 2> /dev/null

# Apply patches; exit if any fail.
apply_patches
if [ ! $? = 0 ]; then
  exit 1
fi

# Make build directory:
mkdir build-glibc-$VERSION
cd build-glibc-$VERSION

echo "BUILDING DAS NPTL GLIBC"
CFLAGS="$OPTIMIZ" \
../configure \
  --prefix=/usr \
  --libdir=/usr/lib${LIBDIRSUFFIX} \
  --enable-kernel=2.6.18 \
  --with-headers=/usr/include \
  --enable-add-ons=nptl,libidn \
  --enable-profile \
  --infodir=/usr/info \
  --mandir=/usr/man \
  --with-tls \
  --with-__thread \
  --without-cvs \
  --without-gd \
  --enable-bind-now \
  --enable-shared \
  $TARGET-slackware-linux

make -j4 > /dev/null || exit 1
make install install_root=$PKG || exit 1
make localedata/install-locales install_root=$PKG || exit 1

# The prevailing standard seems to be putting unstripped libraries in
# /usr/lib/debug/ and stripping the debugging symbols from all the other
# libraries.
mkdir -p $PKG/usr/lib${LIBDIRSUFFIX}/debug
cp -a $PKG/lib${LIBDIRSUFFIX}/l*.so* $PKG/usr/lib${LIBDIRSUFFIX}/debug
cp -a $PKG/usr/lib${LIBDIRSUFFIX}/*.a $PKG/usr/lib${LIBDIRSUFFIX}/debug
# Don't need debug+profile:
( cd $PKG/usr/lib${LIBDIRSUFFIX}/debug ; rm -f *_p.* )
# NOTE:  Is there really a reason for the glibc-debug package?
# If you're debugging glibc, you can also compile it, right?

## COMMENTED OUT:  There's no reason for profile libs to include -g information.
## Put back unstripped profiling libraries:
#mv $PKG/usr/lib${LIBDIRSUFFIX}/debug/*_p.a $PKG/usr/lib${LIBDIRSUFFIX}
# It might be best to put the unstripped and profiling libraries in glibc-debug and glibc-profile.

# I don't think "strip -g" causes the pthread problems.  It's --strip-unneeded that does.
strip -g $PKG/lib${LIBDIRSUFFIX}/l*.so*
strip -g $PKG/usr/lib${LIBDIRSUFFIX}/l*.so*
strip -g $PKG/usr/lib${LIBDIRSUFFIX}/lib*.a

# Back to the sources dir to add some files/docs:
cd $TMP/glibc-$CVSVER

# We'll automatically install the config file for the Name Server Cache Daemon.
# Perhaps this should also have some commented-out startup code in rc.inet2...
mkdir -p $PKG/etc
cat nscd/nscd.conf > $PKG/etc/nscd.conf.new

# Install some scripts to help select a timezone:
mkdir -p $PKG/var/log/setup
cp -a $CWD/timezone-scripts/setup.timeconfig $PKG/var/log/setup
chown root:root $PKG/var/log/setup/setup.timeconfig
chmod 755 $PKG/var/log/setup/setup.timeconfig
mkdir -p $PKG/usr/sbin
cp -a $CWD/timezone-scripts/timeconfig $PKG/usr/sbin
chown root:root $PKG/usr/sbin/timeconfig
chmod 755 $PKG/usr/sbin/timeconfig

## Install docs:
( mkdir -p $PKG/usr/doc/glibc-$VERSION
  cp -a \
    BUGS CONFORMANCE COPYING COPYING.LIB FAQ INSTALL LICENSES NAMESPACE \
    NEWS NOTES PROJECTS README README.libm \
    $PKG/usr/doc/glibc-$VERSION
)

# Don't forget to add the /usr/share/zoneinfo/localtime -> /etc/localtime symlink! :)
if [ ! -r $PKG/usr/share/zoneinfo/localtime ]; then
  ( cd $PKG/usr/share/zoneinfo ; ln -sf /etc/localtime . )
fi

# OK, there are some very old Linux standards that say that any binaries in a /bin or
# /sbin directory (and the directories themselves) should be group bin rather than
# group root, unless a specific group is really needed for some reason.
#
# I can't find any mention of this in more recent standards docs, and always thought
# that it was pretty cosmetic anyway (hey, if there's a reason -- fill me in!), so
# it's possible that this ownership change won't be followed in the near future
# (it's a PITA, and causes many bug reports when the perms change is occasionally
# forgotten).
#
# But, it's hard to get me to break old habits, so we'll continue the tradition here:
#
# No, no we won't.  You know how we love to break traditions.

# Strip most binaries:
( cd $PKG
  find . | xargs file | grep "executable" | grep ELF | cut -f 1 -d : | xargs strip --strip-debug 2> /dev/null
  find . | xargs file | grep "shared object" | grep ELF | cut -f 1 -d : | xargs strip -g 2> /dev/null
)

# Fix info dir:
rm $PKG/usr/info/dir
gzip -9 $PKG/usr/info/*

# This is junk
rm $PKG/etc/ld.so.cache
( cd $PKG
  find . -name "*.orig" -exec rm {} \;
)

##################################
# OK, time to make some packages #
##################################

# glibc-zoneinfo.  We will start with an easy one to avoid breaking a sweat.  ;-)
cd $PZONE
# Install some scripts to help select a timezone:
mkdir -p $PZONE/var/log/setup
cp -a $CWD/timezone-scripts/setup.timeconfig $PZONE/var/log/setup
chown root:root $PZONE/var/log/setup/setup.timeconfig
chmod 755 $PZONE/var/log/setup/setup.timeconfig
mkdir -p $PZONE/usr/sbin
cp -a $CWD/timezone-scripts/timeconfig $PZONE/usr/sbin
chown root:root $PZONE/usr/sbin/timeconfig
chmod 755 $PZONE/usr/sbin/timeconfig
mkdir $PZONE/install
cat $CWD/doinst.sh-glibc-zoneinfo > $PZONE/install/doinst.sh
cat $CWD/slack-desc.glibc-zoneinfo > $PZONE/install/slack-desc
mkdir -p $PZONE/usr/share
cd $PZONE/usr/share
cp -a --verbose $PKG/usr/share/zoneinfo .
cd $PZONE
mkdir -p $PZONE/etc
# This is already hard-coded into doinst.sh (like it'll be there anyway ;-):
rm -f etc/localtime
# Wrap it up:
makepkg -l y -c n $TMP/glibc-zoneinfo-$VERSION-noarch-$BUILD.txz

# glibc-profile:
cd $PPROFILE
mkdir -p usr/lib${LIBDIRSUFFIX}
# Might as well just grab these with 'mv' to simplify things later:
mv $PKG/usr/lib${LIBDIRSUFFIX}/lib*_p.a usr/lib${LIBDIRSUFFIX}
# Profile libs should be stripped.  Use the debug libs to debug...
( cd usr/lib${LIBDIRSUFFIX} ; strip -g *.a )
mkdir install
cp -a $CWD/slack-desc.glibc-profile install/slack-desc
makepkg -l y -c n $TMP/glibc-profile-$VERSION-$ARCH-$BUILD.txz

# THIS IS NO LONGER PACKAGED (or is it?  might be better to let it be made, and then ship it or not...)
# glibc-debug:
cd $PDEBUG
mkdir -p usr/lib${LIBDIRSUFFIX}
# Might as well just grab these with 'mv' to simplify things later:
mv $PKG/usr/lib${LIBDIRSUFFIX}/debug usr/lib${LIBDIRSUFFIX}
mkdir install
cp -a $CWD/slack-desc.glibc-debug install/slack-desc
makepkg -l y -c n $TMP/glibc-debug-$VERSION-$ARCH-$BUILD.txz
## INSTEAD, NUKE THESE LIBS
#rm -rf $PKG/usr/lib${LIBDIRSUFFIX}/debug

# glibc-i18n:
cd $PI18N
mkdir -p usr/lib${LIBDIRSUFFIX}
rm -rf usr/lib${LIBDIRSUFFIX}/locale
cp -a $PKG/usr/lib${LIBDIRSUFFIX}/locale usr/lib${LIBDIRSUFFIX}
mkdir -p usr/share
cp -a $PKG/usr/share/i18n usr/share
cp -a $PKG/usr/share/locale usr/share
mkdir install
cp -a $CWD/slack-desc.glibc-i18n install/slack-desc
makepkg -l y -c n $TMP/glibc-i18n-$VERSION-$ARCH-$BUILD.txz

# glibc-solibs:
cd $PSOLIBS
mkdir -p etc/profile.d
cp -a $CWD/profile.d/* etc/profile.d
chown -R root:root etc
chmod 755 etc/profile.d/*
mkdir -p lib${LIBDIRSUFFIX}
cp -a $PKG/lib${LIBDIRSUFFIX}/* lib${LIBDIRSUFFIX}
( cd lib${LIBDIRSUFFIX}
  mkdir incoming
  mv *so* incoming
  mv incoming/libSegFault.so .
)
mkdir -p usr
cp -a $PKG/usr/bin usr
mv usr/bin/ldd .
rm usr/bin/*
mv ldd usr/bin
mkdir -p usr/lib${LIBDIRSUFFIX}
# The gconv directory has a lot of stuff, but including it here will save some problems.
# Seems standard elsewhere.
cp -a $PKG/usr/lib${LIBDIRSUFFIX}/gconv usr/lib${LIBDIRSUFFIX}
# Another manpage abandoned by GNU...
#mkdir -p usr/man/man1
#cp -a $PKG/usr/man/man1/ldd.1.gz usr/man/man1
mkdir -p usr/libexec
cp -a $PKG/usr/libexec/pt_chown usr/libexec
# Same usr.bin deal:
cp -a $PKG/sbin .
mv sbin/ldconfig .
rm sbin/*
mv ldconfig sbin
mkdir install
cp -a $CWD/slack-desc.glibc-solibs install/slack-desc
cp -a $CWD/doinst.sh-glibc-solibs install/doinst.sh
fix_doinst
# Ditch links:
find . -type l -exec rm {} \;
# Build the package:
makepkg -l y -c n $TMP/glibc-solibs-$VERSION-$ARCH-$BUILD.txz

# And finally, the complete "all-in-one" glibc package is created
# from whatever was leftover:
cd $PGLIBC
mv $PKG/* .
mkdir -p etc/profile.d
cp -a $CWD/profile.d/* etc/profile.d
chown -R root:root etc
chmod 755 etc/profile.d/*
# Ditch links (these are in doinst.sh-glibc):
find . -type l -exec rm {} \;
mkdir install
cp -a $CWD/slack-desc.glibc install/slack-desc
cp -a $CWD/doinst.sh-glibc install/doinst.sh
fix_doinst
( cd lib${LIBDIRSUFFIX}
  mkdir incoming
  mv *so* incoming
  mv incoming/libSegFault.so .
)
# Build the package:
makepkg -l y -c n $TMP/glibc-$VERSION-$ARCH-$BUILD.txz

# Done!
echo
echo "glibc packages built in $TMP!"

Le cose più importanti da aggiungere sono --enable-bind-now e --enable-shared. Infati mettere solo i686 non serve ad abilitare le SYSENTER/SYSEXIT.

Domande

  1. Ma se in SlackWare non è abilitata la Sysenter un motivo ci sara? A mio dire NO. Si potrebbe ipotizzare che per mantenere la compatibilità con sistemi antecedenti al Pentium2 si sia optato per mantenere l'INT 80h nelle librerie glibc. Ma ciò è insensato perchè la compatibilità è garantita dal kernel. Supponiamo infatti di abilitare le sysenter in SlackWare ricompilando e installando i pacchetti .txz. Se voglio installare poi il sistema su un Pentium1 posso farlo semplicemente caricando il kernel con il parametro nosep. Così facendo, l'indirizzo __kernel_vsyscall viene spostato in modo da eseguire l'istruzione INT 80h. Per capire questo basta estrarre il VDSO da un vostro processo e disassemblarlo totalmente (con objdump -D file.dso), come spiegato in molti articoli in internet.
  2. Cambia qualche cosa se ottimizzo per Pentium4, piuttosto che per i686? Dai test effettuati da noi non cambia assolutamente nulla. Questo soprattuttoperchè non vengono ustate librerie matematiche nelle glibc e nel kernel e neppure le istruzioni multimediali come MMX, SSE, SSE2, ecc. I nostri test però non tengono conto di opzioni come --enable-fp (nel ./configure) che, unito agli adeguati parametri del compilatore (come -mmmx, -msse, ecc) potrebbero forse dare dei benefici sulle librerie matemetiche, le quali però non sono state testate.
  3. Insisto: voglio ottimizzare per Pentium4!: basta sostituire -march=i686 con -march=pentium4, ma ripeto: non cambia nulla in questa versione delle glibc.
  4. Esistono distribizioni che usano già questa ottimizzazione? Sì, per esempio ARCH Linux, che è anche abbastanza simile a SlackWare, ma in teoria tutte le distribuzioni ottimizzate per i686 dovrebbero implementare queste istruzioni.
  5. Slackware 13 a 64 bit cosa fa? Fa le cose come si deve e implementa l'istruzine SYSCALL. Lo si verifica semplicemente disassemblando un qualsiasi programma compilato staticamente. Questa istruzione, in linea teorica, permette di ridurre molte istruzioni e quindi di ottimizzare meglio il codice.

CONTROINDICAZIONI

L'unico problema rilevato fino ad ora nell'aggiornare le glibc è che il kernel di default della slackware (2.6.29.6 nel caso di Slack13) dovrebbe essere ricompilato se si volesse ricompilare il diver proprietario di NVIDIA. Il problema non sussiste se siete abituati a ricompilarvi il kernel e ne avete uno più aggiornato.

E per sistemi a 64bit?

Per sistemi x86_64, AMD (e non Intell) ha introdotto le istruzioni SYSCALL e SYSRET adottate subito dopo anche da Intell per l'estensione a 64 bit dei suoi processori (NON mi riferisco all'architettura IA64). Queste istruzioni sono gestite in modo più performante in quanto evitano la call all'indirizzo __kernel_vsyscall, ma vengono chiamate subito nel processo al posto della INT 80h. Sfortunatamente non sono stati ancora fatti (da noi) test di performance su questa istruzione.

Link Utili e documentazione

Info&Credits

Tutti i test di compilazione e performance sono stati effettuati alla sede del LUGMan sull'hardware messo a disposizione dall'associazione e dai soci in generale. Per qualsiasi informazione, chiarimento o curiosità contattateci!