Initial commit; kernel source import

This commit is contained in:
Nathan
2025-04-06 23:50:55 -05:00
commit 25c6d769f4
45093 changed files with 18199410 additions and 0 deletions

View File

@@ -0,0 +1,219 @@
#!/bin/bash
#
# NAME
# failcmd.sh - run a command with injecting slab/page allocation failures
#
# SYNOPSIS
# failcmd.sh --help
# failcmd.sh [<options>] command [arguments]
#
# DESCRIPTION
# Run command with injecting slab/page allocation failures by fault
# injection.
#
# NOTE: you need to run this script as root.
#
usage()
{
cat >&2 <<EOF
Usage: $0 [options] command [arguments]
OPTIONS
-p percent
--probability=percent
likelihood of failure injection, in percent.
Default value is 1
-t value
--times=value
specifies how many times failures may happen at most.
Default value is 1
--oom-kill-allocating-task=value
set /proc/sys/vm/oom_kill_allocating_task to specified value
before running the command.
Default value is 1
-h, --help
Display a usage message and exit
--interval=value, --space=value, --verbose=value, --task-filter=value,
--stacktrace-depth=value, --require-start=value, --require-end=value,
--reject-start=value, --reject-end=value, --ignore-gfp-wait=value
See Documentation/fault-injection/fault-injection.txt for more
information
failslab options:
--cache-filter=value
fail_page_alloc options:
--ignore-gfp-highmem=value, --min-order=value
ENVIRONMENT
FAILCMD_TYPE
The following values for FAILCMD_TYPE are recognized:
failslab
inject slab allocation failures
fail_page_alloc
inject page allocation failures
If FAILCMD_TYPE is not defined, then failslab is used.
EOF
}
if [ $UID != 0 ]; then
echo must be run as root >&2
exit 1
fi
DEBUGFS=`mount -t debugfs | head -1 | awk '{ print $3}'`
if [ ! -d "$DEBUGFS" ]; then
echo debugfs is not mounted >&2
exit 1
fi
FAILCMD_TYPE=${FAILCMD_TYPE:-failslab}
FAULTATTR=$DEBUGFS/$FAILCMD_TYPE
if [ ! -d $FAULTATTR ]; then
echo $FAILCMD_TYPE is not available >&2
exit 1
fi
LONGOPTS=probability:,interval:,times:,space:,verbose:,task-filter:
LONGOPTS=$LONGOPTS,stacktrace-depth:,require-start:,require-end:
LONGOPTS=$LONGOPTS,reject-start:,reject-end:,oom-kill-allocating-task:,help
if [ $FAILCMD_TYPE = failslab ]; then
LONGOPTS=$LONGOPTS,ignore-gfp-wait:,cache-filter:
elif [ $FAILCMD_TYPE = fail_page_alloc ]; then
LONGOPTS=$LONGOPTS,ignore-gfp-wait:,ignore-gfp-highmem:,min-order:
fi
TEMP=`getopt -o p:i:t:s:v:h --long $LONGOPTS -n 'failcmd.sh' -- "$@"`
if [ $? != 0 ]; then
usage
exit 1
fi
eval set -- "$TEMP"
fault_attr_default()
{
echo N > $FAULTATTR/task-filter
echo 0 > $FAULTATTR/probability
echo 1 > $FAULTATTR/times
}
fault_attr_default
oom_kill_allocating_task_saved=`cat /proc/sys/vm/oom_kill_allocating_task`
restore_values()
{
fault_attr_default
echo $oom_kill_allocating_task_saved \
> /proc/sys/vm/oom_kill_allocating_task
}
#
# Default options
#
declare -i oom_kill_allocating_task=1
declare task_filter=Y
declare -i probability=1
declare -i times=1
while true; do
case "$1" in
-p|--probability)
probability=$2
shift 2
;;
-i|--interval)
echo $2 > $FAULTATTR/interval
shift 2
;;
-t|--times)
times=$2
shift 2
;;
-s|--space)
echo $2 > $FAULTATTR/space
shift 2
;;
-v|--verbose)
echo $2 > $FAULTATTR/verbose
shift 2
;;
--task-filter)
task_filter=$2
shift 2
;;
--stacktrace-depth)
echo $2 > $FAULTATTR/stacktrace-depth
shift 2
;;
--require-start)
echo $2 > $FAULTATTR/require-start
shift 2
;;
--require-end)
echo $2 > $FAULTATTR/require-end
shift 2
;;
--reject-start)
echo $2 > $FAULTATTR/reject-start
shift 2
;;
--reject-end)
echo $2 > $FAULTATTR/reject-end
shift 2
;;
--oom-kill-allocating-task)
oom_kill_allocating_task=$2
shift 2
;;
--ignore-gfp-wait)
echo $2 > $FAULTATTR/ignore-gfp-wait
shift 2
;;
--cache-filter)
echo $2 > $FAULTATTR/cache_filter
shift 2
;;
--ignore-gfp-highmem)
echo $2 > $FAULTATTR/ignore-gfp-highmem
shift 2
;;
--min-order)
echo $2 > $FAULTATTR/min-order
shift 2
;;
-h|--help)
usage
exit 0
shift
;;
--)
shift
break
;;
esac
done
[ -z "$1" ] && exit 0
echo $oom_kill_allocating_task > /proc/sys/vm/oom_kill_allocating_task
echo $task_filter > $FAULTATTR/task-filter
echo $probability > $FAULTATTR/probability
echo $times > $FAULTATTR/times
trap "restore_values" SIGINT SIGTERM EXIT
cmd="echo 1 > /proc/self/make-it-fail && exec $@"
bash -c "$cmd"

View File

@@ -0,0 +1,32 @@
#!/usr/bin/perl
open (IN,"ktest.pl");
while (<IN>) {
# hashes are now used
if (/\$opt\{"?([A-Z].*?)(\[.*\])?"?\}/ ||
/^\s*"?([A-Z].*?)"?\s*=>\s*/ ||
/set_test_option\("(.*?)"/) {
$opt{$1} = 1;
}
}
close IN;
open (IN, "sample.conf");
while (<IN>) {
if (/^\s*#?\s*([A-Z]\S*)\s*=/) {
$samp{$1} = 1;
}
}
close IN;
foreach $opt (keys %opt) {
if (!defined($samp{$opt})) {
print "opt = $opt\n";
}
}
foreach $samp (keys %samp) {
if (!defined($opt{$samp})) {
print "samp = $samp\n";
}
}

View File

@@ -0,0 +1,32 @@
This directory contains example configs to use ktest for various tasks.
The configs still need to be customized for your environment, but it
is broken up by task which makes it easier to understand how to set up
ktest.
The configs are based off of real working configs but have been modified
and commented to show more generic use cases that are more helpful for
developers.
crosstests.conf - this config shows an example of testing a git repo against
lots of different architectures. It only does build tests, but makes
it easy to compile test different archs. You can download the arch
cross compilers from:
http://kernel.org/pub/tools/crosstool/files/bin/x86_64/
test.conf - A generic example of a config. This is based on an actual config
used to perform real testing.
kvm.conf - A example of a config that is used to test a virtual guest running
on a host.
snowball.conf - An example config that was used to demo ktest.pl against
a snowball ARM board.
include/ - The include directory holds default configs that can be
included into other configs. This is a real use example that shows how
to reuse configs for various machines or set ups. The files here
are included by other config files, where the other config files define
options and variables that will make the included config work for the
given environment.

View File

@@ -0,0 +1,260 @@
#
# Example config for cross compiling
#
# In this config, it is expected that the tool chains from:
#
# http://kernel.org/pub/tools/crosstool/files/bin/x86_64/
#
# running on a x86_64 system have been downloaded and installed into:
#
# /usr/local/
#
# such that the compiler binaries are something like:
#
# /usr/local/gcc-4.5.2-nolibc/mips-linux/bin/mips-linux-gcc
#
# Some of the archs will use gcc-4.5.1 instead of gcc-4.5.2
# this config uses variables to differentiate them.
#
# Comments describe some of the options, but full descriptions of
# options are described in the samples.conf file.
# ${PWD} is defined by ktest.pl to be the directory that the user
# was in when they executed ktest.pl. It may be better to hardcode the
# path name here. THIS_DIR is the variable used through out the config file
# in case you want to change it.
THIS_DIR := ${PWD}
# Update the BUILD_DIR option to the location of your git repo you want to test.
BUILD_DIR = ${THIS_DIR}/linux.git
# The build will go into this directory. It will be created when you run the test.
OUTPUT_DIR = ${THIS_DIR}/cross-compile
# The build will be compiled with -j8
BUILD_OPTIONS = -j8
# The test will not stop when it hits a failure.
DIE_ON_FAILURE = 0
# If you want to have ktest.pl store the failure somewhere, uncomment this option
# and change the directory where ktest should store the failures.
#STORE_FAILURES = ${THIS_DIR}/failures
# The log file is stored in the OUTPUT_DIR called cross.log
# If you enable this, you need to create the OUTPUT_DIR. It wont be created for you.
LOG_FILE = ${OUTPUT_DIR}/cross.log
# The log file will be cleared each time you run ktest.
CLEAR_LOG = 1
# As some archs do not build with the defconfig, they have been marked
# to be ignored. If you want to test them anyway, change DO_FAILED to 1.
# If a test that has been marked as DO_FAILED passes, then you should change
# that test to be DO_DEFAULT
DO_FAILED := 0
DO_DEFAULT := 1
# By setting both DO_FAILED and DO_DEFAULT to zero, you can pick a single
# arch that you want to test. (uncomment RUN and chose your arch)
#RUN := m32r
# At the bottom of the config file exists a bisect test. You can update that
# test and set DO_FAILED and DO_DEFAULT to zero, and uncomment this variable
# to run the bisect on the arch.
#RUN := bisect
# By default all tests will be running gcc 4.5.2. Some tests are using 4.5.1
# and they select that in the test.
# Note: GCC_VER is declared as on option and not a variable ('=' instead of ':=')
# This is important. A variable is used only in the config file and if it is set
# it stays that way for the rest of the config file until it is change again.
# Here we want GCC_VER to remain persistent and change for each test, as it is used in
# the MAKE_CMD. By using '=' instead of ':=' we achieve our goal.
GCC_VER = 4.5.2
MAKE_CMD = PATH=/usr/local/gcc-${GCC_VER}-nolibc/${CROSS}/bin:$PATH CROSS_COMPILE=${CROSS}- make ARCH=${ARCH}
# all tests are only doing builds.
TEST_TYPE = build
# If you want to add configs on top of the defconfig, you can add those configs into
# the add-config file and uncomment this option. This is useful if you want to test
# all cross compiles with PREEMPT set, or TRACING on, etc.
#ADD_CONFIG = ${THIS_DIR}/add-config
# All tests are using defconfig
BUILD_TYPE = defconfig
# The test names will have the arch and cross compiler used. This will be shown in
# the results.
TEST_NAME = ${ARCH} ${CROSS}
# alpha
TEST_START IF ${RUN} == alpha || ${DO_DEFAULT}
# Notice that CROSS and ARCH are also options and not variables (again '=' instead
# of ':='). This is because TEST_NAME and MAKE_CMD wil use them for each test.
# Only options are available during runs. Variables are only present in parsing the
# config file.
CROSS = alpha-linux
ARCH = alpha
# arm
TEST_START IF ${RUN} == arm || ${DO_DEFAULT}
CROSS = arm-unknown-linux-gnueabi
ARCH = arm
# black fin
TEST_START IF ${RUN} == bfin || ${DO_DEFAULT}
CROSS = bfin-uclinux
ARCH = blackfin
BUILD_OPTIONS = -j8 vmlinux
# cris - FAILS?
TEST_START IF ${RUN} == cris || ${RUN} == cris64 || ${DO_FAILED}
CROSS = cris-linux
ARCH = cris
# cris32 - not right arch?
TEST_START IF ${RUN} == cris || ${RUN} == cris32 || ${DO_FAILED}
CROSS = crisv32-linux
ARCH = cris
# ia64
TEST_START IF ${RUN} == ia64 || ${DO_DEFAULT}
CROSS = ia64-linux
ARCH = ia64
# frv
TEST_START IF ${RUN} == frv || ${DO_FAILED}
CROSS = frv-linux
ARCH = frv
GCC_VER = 4.5.1
# h8300 - failed make defconfig??
TEST_START IF ${RUN} == h8300 || ${DO_FAILED}
CROSS = h8300-elf
ARCH = h8300
GCC_VER = 4.5.1
# m68k fails with error?
TEST_START IF ${RUN} == m68k || ${DO_DEFAULT}
CROSS = m68k-linux
ARCH = m68k
# mips64
TEST_START IF ${RUN} == mips || ${RUN} == mips64 || ${DO_DEFAULT}
CROSS = mips64-linux
ARCH = mips
# mips32
TEST_START IF ${RUN} == mips || ${RUN} == mips32 || ${DO_DEFAULT}
CROSS = mips-linux
ARCH = mips
# m32r
TEST_START IF ${RUN} == m32r || ${DO_FAILED}
CROSS = m32r-linux
ARCH = m32r
GCC_VER = 4.5.1
BUILD_OPTIONS = -j8 vmlinux
# parisc64 failed?
TEST_START IF ${RUN} == hppa || ${RUN} == hppa64 || ${DO_FAILED}
CROSS = hppa64-linux
ARCH = parisc
# parisc
TEST_START IF ${RUN} == hppa || ${RUN} == hppa32 || ${DO_FAILED}
CROSS = hppa-linux
ARCH = parisc
# ppc
TEST_START IF ${RUN} == ppc || ${RUN} == ppc32 || ${DO_DEFAULT}
CROSS = powerpc-linux
ARCH = powerpc
# ppc64
TEST_START IF ${RUN} == ppc || ${RUN} == ppc64 || ${DO_DEFAULT}
CROSS = powerpc64-linux
ARCH = powerpc
# s390
TEST_START IF ${RUN} == s390 || ${DO_DEFAULT}
CROSS = s390x-linux
ARCH = s390
# sh
TEST_START IF ${RUN} == sh || ${DO_DEFAULT}
CROSS = sh4-linux
ARCH = sh
# sparc64
TEST_START IF ${RUN} == sparc || ${RUN} == sparc64 || ${DO_DEFAULT}
CROSS = sparc64-linux
ARCH = sparc64
# sparc
TEST_START IF ${RUN} == sparc || ${RUN} == sparc32 || ${DO_DEFAULT}
CROSS = sparc-linux
ARCH = sparc
# xtensa failed
TEST_START IF ${RUN} == xtensa || ${DO_FAILED}
CROSS = xtensa-linux
ARCH = xtensa
# UML
TEST_START IF ${RUN} == uml || ${DO_DEFAULT}
MAKE_CMD = make ARCH=um SUBARCH=x86_64
ARCH = uml
CROSS =
TEST_START IF ${RUN} == x86 || ${RUN} == i386 || ${DO_DEFAULT}
MAKE_CMD = make ARCH=i386
ARCH = i386
CROSS =
TEST_START IF ${RUN} == x86 || ${RUN} == x86_64 || ${DO_DEFAULT}
MAKE_CMD = make ARCH=x86_64
ARCH = x86_64
CROSS =
#################################
# This is a bisect if needed. You need to give it a MIN_CONFIG that
# will be the config file it uses. Basically, just copy the created defconfig
# for the arch someplace and point MIN_CONFIG to it.
TEST_START IF ${RUN} == bisect
MIN_CONFIG = ${THIS_DIR}/min-config
CROSS = s390x-linux
ARCH = s390
TEST_TYPE = bisect
BISECT_TYPE = build
BISECT_GOOD = v3.1
BISECT_BAD = v3.2
CHECKOUT = v3.2
#################################
# These defaults are needed to keep ktest.pl from complaining. They are
# ignored because the test does not go pass the build. No install or
# booting of the target images.
DEFAULTS
MACHINE = crosstest
SSH_USER = root
BUILD_TARGET = cross
TARGET_IMAGE = image
POWER_CYCLE = cycle
CONSOLE = console
LOCALVERSION = version
GRUB_MENU = grub
REBOOT_ON_ERROR = 0
POWEROFF_ON_ERROR = 0
POWEROFF_ON_SUCCESS = 0
REBOOT_ON_SUCCESS = 0

View File

@@ -0,0 +1,90 @@
#
# This example shows the bisect tests (git bisect and config bisect)
#
# The config that includes this file may define a RUN_TEST
# variable that will tell this config what test to run.
# (what to set the TEST option to).
#
DEFAULTS IF NOT DEFINED RUN_TEST
# Requires that hackbench is in the PATH
RUN_TEST := ${SSH} hackbench 50
# Set TEST to 'bisect' to do a normal git bisect. You need
# to modify the options below to make it bisect the exact
# commits you are interested in.
#
TEST_START IF ${TEST} == bisect
TEST_TYPE = bisect
# You must set the commit that was considered good (git bisect good)
BISECT_GOOD = v3.3
# You must set the commit that was considered bad (git bisect bad)
BISECT_BAD = HEAD
# It's best to specify the branch to checkout before starting the bisect.
CHECKOUT = origin/master
# This can be build, boot, or test. Here we are doing a bisect
# that requires to run a test to know if the bisect was good or bad.
# The test should exit with 0 on good, non-zero for bad. But see
# the BISECT_RET_* options in samples.conf to override this.
BISECT_TYPE = test
TEST = ${RUN_TEST}
# It is usually a good idea to confirm that the GOOD and the BAD
# commits are truly good and bad respectively. Having BISECT_CHECK
# set to 1 will check both that the good commit works and the bad
# commit fails. If you only want to check one or the other,
# set BISECT_CHECK to 'good' or to 'bad'.
BISECT_CHECK = 1
#BISECT_CHECK = good
#BISECT_CHECK = bad
# Usually it's a good idea to specify the exact config you
# want to use throughout the entire bisect. Here we placed
# it in the directory we called ktest.pl from and named it
# 'config-bisect'.
MIN_CONFIG = ${THIS_DIR}/config-bisect
# By default, if we are doing a BISECT_TYPE = test run but the
# build or boot fails, ktest.pl will do a 'git bisect skip'.
# Uncomment the below option to make ktest stop testing on such
# an error.
#BISECT_SKIP = 0
# Now if you had BISECT_SKIP = 0 and the test fails, you can
# examine what happened and then do 'git bisect log > /tmp/replay'
# Set BISECT_REPLAY to /tmp/replay and ktest.pl will run the
# 'git bisect replay /tmp/replay' before continuing the bisect test.
#BISECT_REPLAY = /tmp/replay
# If you used BISECT_REPLAY after the bisect test failed, you may
# not want to continue the bisect on that commit that failed.
# By setting BISECT_START to a new commit. ktest.pl will checkout
# that commit after it has performed the 'git bisect replay' but
# before it continues running the bisect test.
#BISECT_START = 2545eb6198e7e1ec50daa0cfc64a4cdfecf24ec9
# Now if you don't trust ktest.pl to make the decisions for you, then
# set BISECT_MANUAL to 1. This will cause ktest.pl not to decide
# if the commit was good or bad. Instead, it will ask you to tell
# it if the current commit was good. In the mean time, you could
# take the result, load it on any machine you want. Run several tests,
# or whatever you feel like. Then, when you are happy, you can tell
# ktest if you think it was good or not and ktest.pl will continue
# the git bisect. You can even change what commit it is currently at.
#BISECT_MANUAL = 1
# One of the unique tests that ktest does is the config bisect.
# Currently (which hopefully will be fixed soon), the bad config
# must be a superset of the good config. This is because it only
# searches for a config that causes the target to fail. If the
# good config is not a subset of the bad config, or if the target
# fails because of a lack of a config, then it will not find
# the config for you.
TEST_START IF ${TEST} == config-bisect
TEST_TYPE = config_bisect
# set to build, boot, test
CONFIG_BISECT_TYPE = boot
# Set the config that is considered bad.
CONFIG_BISECT = ${THIS_DIR}/config-bad
# This config is optional. By default it uses the
# MIN_CONFIG as the good config.
CONFIG_BISECT_GOOD = ${THIS_DIR}/config-good

View File

@@ -0,0 +1,157 @@
# This file holds defaults for most the tests. It defines the options that
# are most common to tests that are likely to be shared.
#
# Note, after including this file, a config file may override any option
# with a DEFAULTS OVERRIDE section.
#
# For those cases that use the same machine to boot a 64 bit
# and a 32 bit version. The MACHINE is the DNS name to get to the
# box (usually different if it was 64 bit or 32 bit) but the
# BOX here is defined as a variable that will be the name of the box
# itself. It is useful for calling scripts that will power cycle
# the box, as only one script needs to be created to power cycle
# even though the box itself has multiple operating systems on it.
# By default, BOX and MACHINE are the same.
DEFAULTS IF NOT DEFINED BOX
BOX := ${MACHINE}
# Consider each box as 64 bit box, unless the config including this file
# has defined BITS = 32
DEFAULTS IF NOT DEFINED BITS
BITS := 64
DEFAULTS
# THIS_DIR is used through out the configs and defaults to ${PWD} which
# is the directory that ktest.pl was called from.
THIS_DIR := ${PWD}
# to organize your configs, having each machine save their configs
# into a separate directly is useful.
CONFIG_DIR := ${THIS_DIR}/configs/${MACHINE}
# Reset the log before running each test.
CLEAR_LOG = 1
# As installing kernels usually requires root privilege, default the
# user on the target as root. It is also required that the target
# allows ssh to root from the host without asking for a password.
SSH_USER = root
# For accesing the machine, we will ssh to root@machine.
SSH := ssh ${SSH_USER}@${MACHINE}
# Update this. The default here is ktest will ssh to the target box
# and run a script called 'run-test' located on that box.
TEST = ${SSH} run-test
# Point build dir to the git repo you use
BUILD_DIR = ${THIS_DIR}/linux.git
# Each machine will have its own output build directory.
OUTPUT_DIR = ${THIS_DIR}/build/${MACHINE}
# Yes this config is focused on x86 (but ktest works for other archs too)
BUILD_TARGET = arch/x86/boot/bzImage
TARGET_IMAGE = /boot/vmlinuz-test
# have directory for the scripts to reboot and power cycle the boxes
SCRIPTS_DIR := ${THIS_DIR}/scripts
# You can have each box/machine have a script to power cycle it.
# Name your script <box>-cycle.
POWER_CYCLE = ${SCRIPTS_DIR}/${BOX}-cycle
# This script is used to power off the box.
POWER_OFF = ${SCRIPTS_DIR}/${BOX}-poweroff
# Keep your test kernels separate from your other kernels.
LOCALVERSION = -test
# The /boot/grub/menu.lst is searched for the line:
# title Test Kernel
# and ktest will use that kernel to reboot into.
# For grub2 or other boot loaders, you need to set BOOT_TYPE
# to 'script' and define other ways to load the kernel.
# See snowball.conf example.
#
GRUB_MENU = Test Kernel
# The kernel build will use this option.
BUILD_OPTIONS = -j8
# Keeping the log file with the output dir is convenient.
LOG_FILE = ${OUTPUT_DIR}/${MACHINE}.log
# Each box should have their own minum configuration
# See min-config.conf
MIN_CONFIG = ${CONFIG_DIR}/config-min
# For things like randconfigs, there may be configs you find that
# are already broken, or there may be some configs that you always
# want set. Uncomment ADD_CONFIG and point it to the make config files
# that set the configs you want to keep on (or off) in your build.
# ADD_CONFIG is usually something to add configs to all machines,
# where as, MIN_CONFIG is specific per machine.
#ADD_CONFIG = ${THIS_DIR}/config-broken ${THIS_DIR}/config-general
# To speed up reboots for bisects and patchcheck, instead of
# waiting 60 seconds for the console to be idle, if this line is
# seen in the console output, ktest will know the good kernel has
# finished rebooting and it will be able to continue the tests.
REBOOT_SUCCESS_LINE = ${MACHINE} login:
# The following is different ways to end the test.
# by setting the variable REBOOT to: none, error, fail or
# something else, ktest will power cycle or reboot the target box
# at the end of the tests.
#
# REBOOT := none
# Don't do anything at the end of the test.
#
# REBOOT := error
# Reboot the box if ktest detects an error
#
# REBOOT := fail
# Do not stop on failure, and after all tests are complete
# power off the box (for both success and error)
# This is good to run over a weekend and you don't want to waste
# electricity.
#
DEFAULTS IF ${REBOOT} == none
REBOOT_ON_SUCCESS = 0
REBOOT_ON_ERROR = 0
POWEROFF_ON_ERROR = 0
POWEROFF_ON_SUCCESS = 0
DEFAULTS ELSE IF ${REBOOT} == error
REBOOT_ON_SUCCESS = 0
REBOOT_ON_ERROR = 1
POWEROFF_ON_ERROR = 0
POWEROFF_ON_SUCCESS = 0
DEFAULTS ELSE IF ${REBOOT} == fail
REBOOT_ON_SUCCESS = 0
POWEROFF_ON_ERROR = 1
POWEROFF_ON_SUCCESS = 1
POWEROFF_AFTER_HALT = 120
DIE_ON_FAILURE = 0
# Store the failure information into this directory
# such as the .config, dmesg, and build log.
STORE_FAILURES = ${THIS_DIR}/failures
DEFAULTS ELSE
REBOOT_ON_SUCCESS = 1
REBOOT_ON_ERROR = 1
POWEROFF_ON_ERROR = 0
POWEROFF_ON_SUCCESS = 0

View File

@@ -0,0 +1,60 @@
#
# This file has some examples for creating a MIN_CONFIG.
# (A .config file that is the minimum for a machine to boot, or
# to boot and make a network connection.)
#
# A MIN_CONFIG is very useful as it is the minimum configuration
# needed to boot a given machine. You can debug someone else's
# .config by only setting the configs in your MIN_CONFIG. The closer
# your MIN_CONFIG is to the true minimum set of configs needed to
# boot your machine, the closer the config you test with will be
# to the users config that had the failure.
#
# The make_min_config test allows you to create a MIN_CONFIG that
# is truly the minimum set of configs needed to boot a box.
#
# In this example, the final config will reside in
# ${CONFIG_DIR}/config-new-min and ${CONFIG_DIR}/config-new-min-net.
# Just move one to the location you have set for MIN_CONFIG.
#
# The first test creates a MIN_CONFIG that will be the minimum
# configuration to boot ${MACHINE} and be able to ssh to it.
#
# The second test creates a MIN_CONFIG that will only boot
# the target and most likely will not let you ssh to it. (Notice
# how the second test uses the first test's result to continue with.
# This is because the second test config is a subset of the first).
#
# The ${CONFIG_DIR}/config-skip (and -net) will hold the configs
# that ktest.pl found would not boot the target without them set.
# The config-new-min holds configs that ktest.pl could not test
# directly because another config that was needed to boot the box
# selected them. Sometimes it is possible that this file will hold
# the true minimum configuration. You can test to see if this is
# the case by running the boot test with BOOT_TYPE = allnoconfig and
# setting setting the MIN_CONFIG to ${CONFIG_DIR}/config-skip. If the
# machine still boots, then you can use the config-skip as your MIN_CONFIG.
#
# These tests can run for several hours (and perhaps days).
# It's OK to kill the test with a Ctrl^C. By restarting without
# modifying this config, ktest.pl will notice that the config-new-min(-net)
# exists, and will use that instead as the starting point.
# The USE_OUTPUT_MIN_CONFIG is set to 1 to keep ktest.pl from asking
# you if you want to use the OUTPUT_MIN_CONFIG as the starting point.
# By using the OUTPUT_MIN_CONFIG as the starting point will allow ktest.pl to
# start almost where it left off.
#
TEST_START IF ${TEST} == min-config
TEST_TYPE = make_min_config
OUTPUT_MIN_CONFIG = ${CONFIG_DIR}/config-new-min-net
IGNORE_CONFIG = ${CONFIG_DIR}/config-skip-net
MIN_CONFIG_TYPE = test
TEST = ${SSH} echo hi
USE_OUTPUT_MIN_CONFIG = 1
TEST_START IF ${TEST} == min-config && ${MULTI}
TEST_TYPE = make_min_config
OUTPUT_MIN_CONFIG = ${CONFIG_DIR}/config-new-min
IGNORE_CONFIG = ${CONFIG_DIR}/config-skip
MIN_CONFIG = ${CONFIG_DIR}/config-new-min-net
USE_OUTPUT_MIN_CONFIG = 1

View File

@@ -0,0 +1,111 @@
# patchcheck.conf
#
# This contains a test that takes two git commits and will test each
# commit between the two. The build test will look at what files the
# commit has touched, and if any of those files produce a warning, then
# the build will fail.
# PATCH_START is the commit to begin with and PATCH_END is the commit
# to end with (inclusive). This is similar to doing a git rebase -i PATCH_START~1
# and then testing each commit and doing a git rebase --continue.
# You can use a SHA1, a git tag, or anything that git will accept for a checkout
PATCH_START := HEAD~3
PATCH_END := HEAD
# Use the oldconfig if build_type wasn't defined
DEFAULTS IF NOT DEFINED BUILD_TYPE
DO_BUILD_TYPE := oldconfig
DEFAULTS ELSE
DO_BUILD_TYPE := ${BUILD_TYPE}
DEFAULTS
# Change PATCH_CHECKOUT to be the branch you want to test. The test will
# do a git checkout of this branch before starting. Obviously both
# PATCH_START and PATCH_END must be in this branch (and PATCH_START must
# be contained by PATCH_END).
PATCH_CHECKOUT := test/branch
# Usually it's a good idea to have a set config to use for testing individual
# patches.
PATCH_CONFIG := ${CONFIG_DIR}/config-patchcheck
# Change PATCH_TEST to run some test for each patch. Each commit that is
# tested, after it is built and installed on the test machine, this command
# will be executed. Usually what is done is to ssh to the target box and
# run some test scripts. If you just want to boot test your patches
# comment PATCH_TEST out.
PATCH_TEST := ${SSH} "/usr/local/bin/ktest-test-script"
DEFAULTS IF DEFINED PATCH_TEST
PATCH_TEST_TYPE := test
DEFAULTS ELSE
PATCH_TEST_TYPE := boot
# If for some reason a file has a warning that one of your patches touch
# but you do not care about it, set IGNORE_WARNINGS to that commit(s)
# (space delimited)
#IGNORE_WARNINGS = 39eaf7ef884dcc44f7ff1bac803ca2a1dcf43544 6edb2a8a385f0cdef51dae37ff23e74d76d8a6ce
# Instead of just checking for warnings to files that are changed
# it can be advantageous to check for any new warnings. If a
# header file is changed, it could cause a warning in a file not
# touched by the commit. To detect these kinds of warnings, you
# can use the WARNINGS_FILE option.
#
# If the variable CREATE_WARNINGS_FILE is set, this config will
# enable the WARNINGS_FILE during the patchcheck test. Also,
# before running the patchcheck test, it will create the
# warnings file.
#
DEFAULTS IF DEFINED CREATE_WARNINGS_FILE
WARNINGS_FILE = ${OUTPUT_DIR}/warnings_file
TEST_START IF DEFINED CREATE_WARNINGS_FILE
# WARNINGS_FILE is already set by the DEFAULTS above
TEST_TYPE = make_warnings_file
# Checkout the commit before the patches to test,
# and record all the warnings that exist before the patches
# to test are added
CHECKOUT = ${PATCHCHECK_START}~1
# Force a full build
BUILD_NOCLEAN = 0
BUILD_TYPE = ${DO_BUILD_TYPE}
# If you are running a multi test, and the test failed on the first
# test but on, say the 5th patch. If you want to restart on the
# fifth patch, set PATCH_START1. This will make the first test start
# from this commit instead of the PATCH_START commit.
# Note, do not change this option. Just define PATCH_START1 in the
# top config (the one you pass to ktest.pl), and this will use it,
# otherwise it will just use PATCH_START if PATCH_START1 is not defined.
DEFAULTS IF NOT DEFINED PATCH_START1
PATCH_START1 := ${PATCH_START}
TEST_START IF ${TEST} == patchcheck
TEST_TYPE = patchcheck
MIN_CONFIG = ${PATCH_CONFIG}
TEST = ${PATCH_TEST}
PATCHCHECK_TYPE = ${PATCH_TEST_TYPE}
PATCHCHECK_START = ${PATCH_START1}
PATCHCHECK_END = ${PATCH_END}
CHECKOUT = ${PATCH_CHECKOUT}
BUILD_TYPE = ${DO_BUILD_TYPE}
TEST_START IF ${TEST} == patchcheck && ${MULTI}
TEST_TYPE = patchcheck
MIN_CONFIG = ${PATCH_CONFIG}
TEST = ${PATCH_TEST}
PATCHCHECK_TYPE = ${PATCH_TEST_TYPE}
PATCHCHECK_START = ${PATCH_START}
PATCHCHECK_END = ${PATCH_END}
CHECKOUT = ${PATCH_CHECKOUT}
# Use multi to test different compilers?
MAKE_CMD = CC=gcc-4.5.1 make
BUILD_TYPE = ${DO_BUILD_TYPE}

View File

@@ -0,0 +1,74 @@
#
# This is an example of various tests that you can run
#
# The variable TEST can be of boot, build, randconfig, or test.
#
# Note that TEST is a variable created with ':=' and only exists
# throughout the config processing (not during the tests itself).
#
# The TEST option (defined with '=') is used to tell ktest.pl
# what test to run after a successful boot. The TEST option is
# persistent into the test runs.
#
# The config that includes this file may define a BOOT_TYPE
# variable that tells this config what type of boot test to run.
# If it's not defined, the below DEFAULTS will set the default
# to 'oldconfig'.
#
DEFAULTS IF NOT DEFINED BOOT_TYPE
BOOT_TYPE := oldconfig
# The config that includes this file may define a RUN_TEST
# variable that will tell this config what test to run.
# (what to set the TEST option to).
#
DEFAULTS IF NOT DEFINED RUN_TEST
# Requires that hackbench is in the PATH
RUN_TEST := ${SSH} hackbench 50
# If TEST is set to 'boot' then just build a kernel and boot
# the target.
TEST_START IF ${TEST} == boot
TEST_TYPE = boot
# Notice how we set the BUILD_TYPE option to the BOOT_TYPE variable.
BUILD_TYPE = ${BOOT_TYPE}
# Do not do a make mrproper.
BUILD_NOCLEAN = 1
# If you only want to build the kernel, and perhaps install
# and test it yourself, then just set TEST to build.
TEST_START IF ${TEST} == build
TEST_TYPE = build
BUILD_TYPE = ${BOOT_TYPE}
BUILD_NOCLEAN = 1
# Build, install, boot and test with a randconfg 10 times.
# It is important that you have set MIN_CONFIG in the config
# that includes this file otherwise it is likely that the
# randconfig will not have the necessary configs needed to
# boot your box. This version of the test requires a min
# config that has enough to make sure the target has network
# working.
TEST_START ITERATE 10 IF ${TEST} == randconfig
MIN_CONFIG = ${CONFIG_DIR}/config-min-net
TEST_TYPE = test
BUILD_TYPE = randconfig
TEST = ${RUN_TEST}
# This is the same as above, but only tests to a boot prompt.
# The MIN_CONFIG used here does not need to have networking
# working.
TEST_START ITERATE 10 IF ${TEST} == randconfig && ${MULTI}
TEST_TYPE = boot
BUILD_TYPE = randconfig
MIN_CONFIG = ${CONFIG_DIR}/config-min
MAKE_CMD = make
# This builds, installs, boots and tests the target.
TEST_START IF ${TEST} == test
TEST_TYPE = test
BUILD_TYPE = ${BOOT_TYPE}
TEST = ${RUN_TEST}
BUILD_NOCLEAN = 1

View File

@@ -0,0 +1,88 @@
#
# This config is an example usage of ktest.pl with a kvm guest
#
# The guest is called 'Guest' and this would be something that
# could be run on the host to test a virtual machine target.
MACHINE = Guest
# Use virsh to read the serial console of the guest
CONSOLE = virsh console ${MACHINE}
#*************************************#
# This part is the same as test.conf #
#*************************************#
# The include files will set up the type of test to run. Just set TEST to
# which test you want to run.
#
# TESTS = patchcheck, randconfig, boot, test, config-bisect, bisect, min-config
#
# See the include/*.conf files that define these tests
#
TEST := patchcheck
# Some tests may have more than one test to run. Define MULTI := 1 to run
# the extra tests.
MULTI := 0
# In case you want to differentiate which type of system you are testing
BITS := 64
# REBOOT = none, error, fail, empty
# See include/defaults.conf
REBOOT := empty
# The defaults file will set up various settings that can be used by all
# machine configs.
INCLUDE include/defaults.conf
#*************************************#
# Now we are different from test.conf #
#*************************************#
# The example here assumes that Guest is running a Fedora release
# that uses dracut for its initfs. The POST_INSTALL will be executed
# after the install of the kernel and modules are complete.
#
POST_INSTALL = ${SSH} /sbin/dracut -f /boot/initramfs-test.img $KERNEL_VERSION
# Guests sometimes get stuck on reboot. We wait 3 seconds after running
# the reboot command and then do a full power-cycle of the guest.
# This forces the guest to restart.
#
POWERCYCLE_AFTER_REBOOT = 3
# We do the same after the halt command, but this time we wait 20 seconds.
POWEROFF_AFTER_HALT = 20
# As the defaults.conf file has a POWER_CYCLE option already defined,
# and options can not be defined in the same section more than once
# (all DEFAULTS sections are considered the same). We use the
# DEFAULTS OVERRIDE to tell ktest.pl to ignore the previous defined
# options, for the options set in the OVERRIDE section.
#
DEFAULTS OVERRIDE
# Instead of using the default POWER_CYCLE option defined in
# defaults.conf, we use virsh to cycle it. To do so, we destroy
# the guest, wait 5 seconds, and then start it up again.
# Crude, but effective.
#
POWER_CYCLE = virsh destroy ${MACHINE}; sleep 5; virsh start ${MACHINE}
DEFAULTS
# The following files each handle a different test case.
# Having them included allows you to set up more than one machine and share
# the same tests.
INCLUDE include/patchcheck.conf
INCLUDE include/tests.conf
INCLUDE include/bisect.conf
INCLUDE include/min-config.conf

View File

@@ -0,0 +1,53 @@
# This example was used to boot the snowball ARM board.
# See http://people.redhat.com/srostedt/ktest-embedded-2012/
# PWD is a ktest.pl variable that will result in the process working
# directory that ktest.pl is executed in.
# THIS_DIR is automatically assigned the PWD of the path that generated
# the config file. It is best to use this variable when assigning other
# directory paths within this directory. This allows you to easily
# move the test cases to other locations or to other machines.
#
THIS_DIR := /home/rostedt/work/demo/ktest-embed
LOG_FILE = ${OUTPUT_DIR}/snowball.log
CLEAR_LOG = 1
MAKE_CMD = PATH=/usr/local/gcc-4.5.2-nolibc/arm-unknown-linux-gnueabi/bin:$PATH CROSS_COMPILE=arm-unknown-linux-gnueabi- make ARCH=arm
ADD_CONFIG = ${THIS_DIR}/addconfig
SCP_TO_TARGET = echo "don't do scp"
TFTPBOOT := /var/lib/tftpboot
TFTPDEF := ${TFTPBOOT}/snowball-default
TFTPTEST := ${OUTPUT_DIR}/${BUILD_TARGET}
SWITCH_TO_GOOD = cp ${TFTPDEF} ${TARGET_IMAGE}
SWITCH_TO_TEST = cp ${TFTPTEST} ${TARGET_IMAGE}
# Define each test with TEST_START
# The config options below it will override the defaults
TEST_START SKIP
TEST_TYPE = boot
BUILD_TYPE = u8500_defconfig
BUILD_NOCLEAN = 1
TEST_START
TEST_TYPE = make_min_config
OUTPUT_MIN_CONFIG = ${THIS_DIR}/config.newmin
START_MIN_CONFIG = ${THIS_DIR}/config.orig
IGNORE_CONFIG = ${THIS_DIR}/config.ignore
BUILD_NOCLEAN = 1
DEFAULTS
LOCALVERSION = -test
POWER_CYCLE = echo use the thumb luke; read a
CONSOLE = cat ${THIS_DIR}/snowball-cat
REBOOT_TYPE = script
SSH_USER = root
BUILD_OPTIONS = -j8 uImage
BUILD_DIR = ${THIS_DIR}/linux.git
OUTPUT_DIR = ${THIS_DIR}/snowball-build
MACHINE = snowball
TARGET_IMAGE = /var/lib/tftpboot/snowball-image
BUILD_TARGET = arch/arm/boot/uImage

View File

@@ -0,0 +1,62 @@
#
# Generic config for a machine
#
# Name your machine (the DNS name, what you ssh to)
MACHINE = foo
# BOX can be different than foo, if the machine BOX has
# multiple partitions with different systems installed. For example,
# you may have a i386 and x86_64 installation on a test box.
# If this is the case, MACHINE defines the way to connect to the
# machine, which may be different between which system the machine
# is booting into. BOX is used for the scripts to reboot and power cycle
# the machine, where it does not matter which system the machine boots into.
#
#BOX := bar
# Define a way to read the console
CONSOLE = stty -F /dev/ttyS0 115200 parodd; cat /dev/ttyS0
# The include files will set up the type of test to run. Just set TEST to
# which test you want to run.
#
# TESTS = patchcheck, randconfig, boot, test, config-bisect, bisect, min-config
#
# See the include/*.conf files that define these tests
#
TEST := patchcheck
# Some tests may have more than one test to run. Define MULTI := 1 to run
# the extra tests.
MULTI := 0
# In case you want to differentiate which type of system you are testing
BITS := 64
# REBOOT = none, error, fail, empty
# See include/defaults.conf
REBOOT := empty
# The defaults file will set up various settings that can be used by all
# machine configs.
INCLUDE include/defaults.conf
# In case you need to add a patch for a bisect or something
#PRE_BUILD = patch -p1 < ${THIS_DIR}/fix.patch
# Reset the repo after the build and remove all 'test' modules from the target
# Notice that DO_POST_BUILD is a variable (defined by ':=') and POST_BUILD
# is the option (defined by '=')
DO_POST_BUILD := git reset --hard
POST_BUILD = ${SSH} 'rm -rf /lib/modules/*-test*'; ${DO_POST_BUILD}
# The following files each handle a different test case.
# Having them included allows you to set up more than one machine and share
# the same tests.
INCLUDE include/patchcheck.conf
INCLUDE include/tests.conf
INCLUDE include/bisect.conf
INCLUDE include/min-config.conf

4087
tools/testing/ktest/ktest.pl Executable file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,24 @@
TARGETS = breakpoints
TARGETS += cpu-hotplug
TARGETS += efivarfs
TARGETS += kcmp
TARGETS += memory-hotplug
TARGETS += mqueue
TARGETS += net
TARGETS += ptrace
TARGETS += vm
all:
for TARGET in $(TARGETS); do \
make -C $$TARGET; \
done;
run_tests: all
for TARGET in $(TARGETS); do \
make -C $$TARGET run_tests; \
done;
clean:
for TARGET in $(TARGETS); do \
make -C $$TARGET clean; \
done;

View File

@@ -0,0 +1,42 @@
Linux Kernel Selftests
The kernel contains a set of "self tests" under the tools/testing/selftests/
directory. These are intended to be small unit tests to exercise individual
code paths in the kernel.
Running the selftests
=====================
To build the tests:
$ make -C tools/testing/selftests
To run the tests:
$ make -C tools/testing/selftests run_tests
- note that some tests will require root privileges.
To run only tests targetted for a single subsystem:
$ make -C tools/testing/selftests TARGETS=cpu-hotplug run_tests
See the top-level tools/testing/selftests/Makefile for the list of all possible
targets.
Contributing new tests
======================
In general, the rules for for selftests are
* Do as much as you can if you're not root;
* Don't take too long;
* Don't break the build on any architecture, and
* Don't cause the top-level "make run_tests" to fail if your feature is
unconfigured.

View File

@@ -0,0 +1,23 @@
# Taken from perf makefile
uname_M := $(shell uname -m 2>/dev/null || echo not)
ARCH ?= $(shell echo $(uname_M) | sed -e s/i.86/i386/)
ifeq ($(ARCH),i386)
ARCH := x86
endif
ifeq ($(ARCH),x86_64)
ARCH := x86
endif
all:
ifeq ($(ARCH),x86)
gcc breakpoint_test.c -o breakpoint_test
else
echo "Not an x86 target, can't build breakpoints selftests"
endif
run_tests:
@./breakpoint_test || echo "breakpoints selftests: [FAIL]"
clean:
rm -fr breakpoint_test

View File

@@ -0,0 +1,394 @@
/*
* Copyright (C) 2011 Red Hat, Inc., Frederic Weisbecker <fweisbec@redhat.com>
*
* Licensed under the terms of the GNU GPL License version 2
*
* Selftests for breakpoints (and more generally the do_debug() path) in x86.
*/
#include <sys/ptrace.h>
#include <unistd.h>
#include <stddef.h>
#include <sys/user.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
/* Breakpoint access modes */
enum {
BP_X = 1,
BP_RW = 2,
BP_W = 4,
};
static pid_t child_pid;
/*
* Ensures the child and parent are always "talking" about
* the same test sequence. (ie: that we haven't forgotten
* to call check_trapped() somewhere).
*/
static int nr_tests;
static void set_breakpoint_addr(void *addr, int n)
{
int ret;
ret = ptrace(PTRACE_POKEUSER, child_pid,
offsetof(struct user, u_debugreg[n]), addr);
if (ret) {
perror("Can't set breakpoint addr\n");
exit(-1);
}
}
static void toggle_breakpoint(int n, int type, int len,
int local, int global, int set)
{
int ret;
int xtype, xlen;
unsigned long vdr7, dr7;
switch (type) {
case BP_X:
xtype = 0;
break;
case BP_W:
xtype = 1;
break;
case BP_RW:
xtype = 3;
break;
}
switch (len) {
case 1:
xlen = 0;
break;
case 2:
xlen = 4;
break;
case 4:
xlen = 0xc;
break;
case 8:
xlen = 8;
break;
}
dr7 = ptrace(PTRACE_PEEKUSER, child_pid,
offsetof(struct user, u_debugreg[7]), 0);
vdr7 = (xlen | xtype) << 16;
vdr7 <<= 4 * n;
if (local) {
vdr7 |= 1 << (2 * n);
vdr7 |= 1 << 8;
}
if (global) {
vdr7 |= 2 << (2 * n);
vdr7 |= 1 << 9;
}
if (set)
dr7 |= vdr7;
else
dr7 &= ~vdr7;
ret = ptrace(PTRACE_POKEUSER, child_pid,
offsetof(struct user, u_debugreg[7]), dr7);
if (ret) {
perror("Can't set dr7");
exit(-1);
}
}
/* Dummy variables to test read/write accesses */
static unsigned long long dummy_var[4];
/* Dummy functions to test execution accesses */
static void dummy_func(void) { }
static void dummy_func1(void) { }
static void dummy_func2(void) { }
static void dummy_func3(void) { }
static void (*dummy_funcs[])(void) = {
dummy_func,
dummy_func1,
dummy_func2,
dummy_func3,
};
static int trapped;
static void check_trapped(void)
{
/*
* If we haven't trapped, wake up the parent
* so that it notices the failure.
*/
if (!trapped)
kill(getpid(), SIGUSR1);
trapped = 0;
nr_tests++;
}
static void write_var(int len)
{
char *pcval; short *psval; int *pival; long long *plval;
int i;
for (i = 0; i < 4; i++) {
switch (len) {
case 1:
pcval = (char *)&dummy_var[i];
*pcval = 0xff;
break;
case 2:
psval = (short *)&dummy_var[i];
*psval = 0xffff;
break;
case 4:
pival = (int *)&dummy_var[i];
*pival = 0xffffffff;
break;
case 8:
plval = (long long *)&dummy_var[i];
*plval = 0xffffffffffffffffLL;
break;
}
check_trapped();
}
}
static void read_var(int len)
{
char cval; short sval; int ival; long long lval;
int i;
for (i = 0; i < 4; i++) {
switch (len) {
case 1:
cval = *(char *)&dummy_var[i];
break;
case 2:
sval = *(short *)&dummy_var[i];
break;
case 4:
ival = *(int *)&dummy_var[i];
break;
case 8:
lval = *(long long *)&dummy_var[i];
break;
}
check_trapped();
}
}
/*
* Do the r/w/x accesses to trigger the breakpoints. And run
* the usual traps.
*/
static void trigger_tests(void)
{
int len, local, global, i;
char val;
int ret;
ret = ptrace(PTRACE_TRACEME, 0, NULL, 0);
if (ret) {
perror("Can't be traced?\n");
return;
}
/* Wake up father so that it sets up the first test */
kill(getpid(), SIGUSR1);
/* Test instruction breakpoints */
for (local = 0; local < 2; local++) {
for (global = 0; global < 2; global++) {
if (!local && !global)
continue;
for (i = 0; i < 4; i++) {
dummy_funcs[i]();
check_trapped();
}
}
}
/* Test write watchpoints */
for (len = 1; len <= sizeof(long); len <<= 1) {
for (local = 0; local < 2; local++) {
for (global = 0; global < 2; global++) {
if (!local && !global)
continue;
write_var(len);
}
}
}
/* Test read/write watchpoints (on read accesses) */
for (len = 1; len <= sizeof(long); len <<= 1) {
for (local = 0; local < 2; local++) {
for (global = 0; global < 2; global++) {
if (!local && !global)
continue;
read_var(len);
}
}
}
/* Icebp trap */
asm(".byte 0xf1\n");
check_trapped();
/* Int 3 trap */
asm("int $3\n");
check_trapped();
kill(getpid(), SIGUSR1);
}
static void check_success(const char *msg)
{
const char *msg2;
int child_nr_tests;
int status;
/* Wait for the child to SIGTRAP */
wait(&status);
msg2 = "Failed";
if (WSTOPSIG(status) == SIGTRAP) {
child_nr_tests = ptrace(PTRACE_PEEKDATA, child_pid,
&nr_tests, 0);
if (child_nr_tests == nr_tests)
msg2 = "Ok";
if (ptrace(PTRACE_POKEDATA, child_pid, &trapped, 1)) {
perror("Can't poke\n");
exit(-1);
}
}
nr_tests++;
printf("%s [%s]\n", msg, msg2);
}
static void launch_instruction_breakpoints(char *buf, int local, int global)
{
int i;
for (i = 0; i < 4; i++) {
set_breakpoint_addr(dummy_funcs[i], i);
toggle_breakpoint(i, BP_X, 1, local, global, 1);
ptrace(PTRACE_CONT, child_pid, NULL, 0);
sprintf(buf, "Test breakpoint %d with local: %d global: %d",
i, local, global);
check_success(buf);
toggle_breakpoint(i, BP_X, 1, local, global, 0);
}
}
static void launch_watchpoints(char *buf, int mode, int len,
int local, int global)
{
const char *mode_str;
int i;
if (mode == BP_W)
mode_str = "write";
else
mode_str = "read";
for (i = 0; i < 4; i++) {
set_breakpoint_addr(&dummy_var[i], i);
toggle_breakpoint(i, mode, len, local, global, 1);
ptrace(PTRACE_CONT, child_pid, NULL, 0);
sprintf(buf, "Test %s watchpoint %d with len: %d local: "
"%d global: %d", mode_str, i, len, local, global);
check_success(buf);
toggle_breakpoint(i, mode, len, local, global, 0);
}
}
/* Set the breakpoints and check the child successfully trigger them */
static void launch_tests(void)
{
char buf[1024];
int len, local, global, i;
/* Instruction breakpoints */
for (local = 0; local < 2; local++) {
for (global = 0; global < 2; global++) {
if (!local && !global)
continue;
launch_instruction_breakpoints(buf, local, global);
}
}
/* Write watchpoint */
for (len = 1; len <= sizeof(long); len <<= 1) {
for (local = 0; local < 2; local++) {
for (global = 0; global < 2; global++) {
if (!local && !global)
continue;
launch_watchpoints(buf, BP_W, len,
local, global);
}
}
}
/* Read-Write watchpoint */
for (len = 1; len <= sizeof(long); len <<= 1) {
for (local = 0; local < 2; local++) {
for (global = 0; global < 2; global++) {
if (!local && !global)
continue;
launch_watchpoints(buf, BP_RW, len,
local, global);
}
}
}
/* Icebp traps */
ptrace(PTRACE_CONT, child_pid, NULL, 0);
check_success("Test icebp");
/* Int 3 traps */
ptrace(PTRACE_CONT, child_pid, NULL, 0);
check_success("Test int 3 trap");
ptrace(PTRACE_CONT, child_pid, NULL, 0);
}
int main(int argc, char **argv)
{
pid_t pid;
int ret;
pid = fork();
if (!pid) {
trigger_tests();
return 0;
}
child_pid = pid;
wait(NULL);
launch_tests();
wait(NULL);
return 0;
}

View File

@@ -0,0 +1,6 @@
all:
run_tests:
@./on-off-test.sh || echo "cpu-hotplug selftests: [FAIL]"
clean:

View File

@@ -0,0 +1,221 @@
#!/bin/bash
SYSFS=
prerequisite()
{
msg="skip all tests:"
if [ $UID != 0 ]; then
echo $msg must be run as root >&2
exit 0
fi
SYSFS=`mount -t sysfs | head -1 | awk '{ print $3 }'`
if [ ! -d "$SYSFS" ]; then
echo $msg sysfs is not mounted >&2
exit 0
fi
if ! ls $SYSFS/devices/system/cpu/cpu* > /dev/null 2>&1; then
echo $msg cpu hotplug is not supported >&2
exit 0
fi
}
#
# list all hot-pluggable CPUs
#
hotpluggable_cpus()
{
local state=${1:-.\*}
for cpu in $SYSFS/devices/system/cpu/cpu*; do
if [ -f $cpu/online ] && grep -q $state $cpu/online; then
echo ${cpu##/*/cpu}
fi
done
}
hotplaggable_offline_cpus()
{
hotpluggable_cpus 0
}
hotpluggable_online_cpus()
{
hotpluggable_cpus 1
}
cpu_is_online()
{
grep -q 1 $SYSFS/devices/system/cpu/cpu$1/online
}
cpu_is_offline()
{
grep -q 0 $SYSFS/devices/system/cpu/cpu$1/online
}
online_cpu()
{
echo 1 > $SYSFS/devices/system/cpu/cpu$1/online
}
offline_cpu()
{
echo 0 > $SYSFS/devices/system/cpu/cpu$1/online
}
online_cpu_expect_success()
{
local cpu=$1
if ! online_cpu $cpu; then
echo $FUNCNAME $cpu: unexpected fail >&2
elif ! cpu_is_online $cpu; then
echo $FUNCNAME $cpu: unexpected offline >&2
fi
}
online_cpu_expect_fail()
{
local cpu=$1
if online_cpu $cpu 2> /dev/null; then
echo $FUNCNAME $cpu: unexpected success >&2
elif ! cpu_is_offline $cpu; then
echo $FUNCNAME $cpu: unexpected online >&2
fi
}
offline_cpu_expect_success()
{
local cpu=$1
if ! offline_cpu $cpu; then
echo $FUNCNAME $cpu: unexpected fail >&2
elif ! cpu_is_offline $cpu; then
echo $FUNCNAME $cpu: unexpected offline >&2
fi
}
offline_cpu_expect_fail()
{
local cpu=$1
if offline_cpu $cpu 2> /dev/null; then
echo $FUNCNAME $cpu: unexpected success >&2
elif ! cpu_is_online $cpu; then
echo $FUNCNAME $cpu: unexpected offline >&2
fi
}
error=-12
priority=0
while getopts e:hp: opt; do
case $opt in
e)
error=$OPTARG
;;
h)
echo "Usage $0 [ -e errno ] [ -p notifier-priority ]"
exit
;;
p)
priority=$OPTARG
;;
esac
done
if ! [ "$error" -ge -4095 -a "$error" -lt 0 ]; then
echo "error code must be -4095 <= errno < 0" >&2
exit 1
fi
prerequisite
#
# Online all hot-pluggable CPUs
#
for cpu in `hotplaggable_offline_cpus`; do
online_cpu_expect_success $cpu
done
#
# Offline all hot-pluggable CPUs
#
for cpu in `hotpluggable_online_cpus`; do
offline_cpu_expect_success $cpu
done
#
# Online all hot-pluggable CPUs again
#
for cpu in `hotplaggable_offline_cpus`; do
online_cpu_expect_success $cpu
done
#
# Test with cpu notifier error injection
#
DEBUGFS=`mount -t debugfs | head -1 | awk '{ print $3 }'`
NOTIFIER_ERR_INJECT_DIR=$DEBUGFS/notifier-error-inject/cpu
prerequisite_extra()
{
msg="skip extra tests:"
/sbin/modprobe -q -r cpu-notifier-error-inject
/sbin/modprobe -q cpu-notifier-error-inject priority=$priority
if [ ! -d "$DEBUGFS" ]; then
echo $msg debugfs is not mounted >&2
exit 0
fi
if [ ! -d $NOTIFIER_ERR_INJECT_DIR ]; then
echo $msg cpu-notifier-error-inject module is not available >&2
exit 0
fi
}
prerequisite_extra
#
# Offline all hot-pluggable CPUs
#
echo 0 > $NOTIFIER_ERR_INJECT_DIR/actions/CPU_DOWN_PREPARE/error
for cpu in `hotpluggable_online_cpus`; do
offline_cpu_expect_success $cpu
done
#
# Test CPU hot-add error handling (offline => online)
#
echo $error > $NOTIFIER_ERR_INJECT_DIR/actions/CPU_UP_PREPARE/error
for cpu in `hotplaggable_offline_cpus`; do
online_cpu_expect_fail $cpu
done
#
# Online all hot-pluggable CPUs
#
echo 0 > $NOTIFIER_ERR_INJECT_DIR/actions/CPU_UP_PREPARE/error
for cpu in `hotplaggable_offline_cpus`; do
online_cpu_expect_success $cpu
done
#
# Test CPU hot-remove error handling (online => offline)
#
echo $error > $NOTIFIER_ERR_INJECT_DIR/actions/CPU_DOWN_PREPARE/error
for cpu in `hotpluggable_online_cpus`; do
offline_cpu_expect_fail $cpu
done
echo 0 > $NOTIFIER_ERR_INJECT_DIR/actions/CPU_DOWN_PREPARE/error
/sbin/modprobe -q -r cpu-notifier-error-inject

View File

@@ -0,0 +1,12 @@
CC = $(CROSS_COMPILE)gcc
CFLAGS = -Wall
test_objs = open-unlink create-read
all: $(test_objs)
run_tests: all
@/bin/bash ./efivarfs.sh || echo "efivarfs selftests: [FAIL]"
clean:
rm -f $(test_objs)

View File

@@ -0,0 +1,38 @@
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
int main(int argc, char **argv)
{
const char *path;
char buf[4];
int fd, rc;
if (argc < 2) {
fprintf(stderr, "usage: %s <path>\n", argv[0]);
return EXIT_FAILURE;
}
path = argv[1];
/* create a test variable */
fd = open(path, O_RDWR | O_CREAT, 0600);
if (fd < 0) {
perror("open(O_WRONLY)");
return EXIT_FAILURE;
}
rc = read(fd, buf, sizeof(buf));
if (rc != 0) {
fprintf(stderr, "Reading a new var should return EOF\n");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,198 @@
#!/bin/bash
efivarfs_mount=/sys/firmware/efi/efivars
test_guid=210be57c-9849-4fc7-a635-e6382d1aec27
check_prereqs()
{
local msg="skip all tests:"
if [ $UID != 0 ]; then
echo $msg must be run as root >&2
exit 0
fi
if ! grep -q "^\S\+ $efivarfs_mount efivarfs" /proc/mounts; then
echo $msg efivarfs is not mounted on $efivarfs_mount >&2
exit 0
fi
}
run_test()
{
local test="$1"
echo "--------------------"
echo "running $test"
echo "--------------------"
if [ "$(type -t $test)" = 'function' ]; then
( $test )
else
( ./$test )
fi
if [ $? -ne 0 ]; then
echo " [FAIL]"
rc=1
else
echo " [PASS]"
fi
}
test_create()
{
local attrs='\x07\x00\x00\x00'
local file=$efivarfs_mount/$FUNCNAME-$test_guid
printf "$attrs\x00" > $file
if [ ! -e $file ]; then
echo "$file couldn't be created" >&2
exit 1
fi
if [ $(stat -c %s $file) -ne 5 ]; then
echo "$file has invalid size" >&2
exit 1
fi
}
test_create_empty()
{
local file=$efivarfs_mount/$FUNCNAME-$test_guid
: > $file
if [ ! -e $file ]; then
echo "$file can not be created without writing" >&2
exit 1
fi
}
test_create_read()
{
local file=$efivarfs_mount/$FUNCNAME-$test_guid
./create-read $file
}
test_delete()
{
local attrs='\x07\x00\x00\x00'
local file=$efivarfs_mount/$FUNCNAME-$test_guid
printf "$attrs\x00" > $file
if [ ! -e $file ]; then
echo "$file couldn't be created" >&2
exit 1
fi
rm $file
if [ -e $file ]; then
echo "$file couldn't be deleted" >&2
exit 1
fi
}
# test that we can remove a variable by issuing a write with only
# attributes specified
test_zero_size_delete()
{
local attrs='\x07\x00\x00\x00'
local file=$efivarfs_mount/$FUNCNAME-$test_guid
printf "$attrs\x00" > $file
if [ ! -e $file ]; then
echo "$file does not exist" >&2
exit 1
fi
printf "$attrs" > $file
if [ -e $file ]; then
echo "$file should have been deleted" >&2
exit 1
fi
}
test_open_unlink()
{
local file=$efivarfs_mount/$FUNCNAME-$test_guid
./open-unlink $file
}
# test that we can create a range of filenames
test_valid_filenames()
{
local attrs='\x07\x00\x00\x00'
local ret=0
local file_list="abc dump-type0-11-1-1362436005 1234 -"
for f in $file_list; do
local file=$efivarfs_mount/$f-$test_guid
printf "$attrs\x00" > $file
if [ ! -e $file ]; then
echo "$file could not be created" >&2
ret=1
else
rm $file
fi
done
exit $ret
}
test_invalid_filenames()
{
local attrs='\x07\x00\x00\x00'
local ret=0
local file_list="
-1234-1234-1234-123456789abc
foo
foo-bar
-foo-
foo-barbazba-foob-foob-foob-foobarbazfoo
foo-------------------------------------
-12345678-1234-1234-1234-123456789abc
a-12345678=1234-1234-1234-123456789abc
a-12345678-1234=1234-1234-123456789abc
a-12345678-1234-1234=1234-123456789abc
a-12345678-1234-1234-1234=123456789abc
1112345678-1234-1234-1234-123456789abc"
for f in $file_list; do
local file=$efivarfs_mount/$f
printf "$attrs\x00" 2>/dev/null > $file
if [ -e $file ]; then
echo "Creating $file should have failed" >&2
rm $file
ret=1
fi
done
exit $ret
}
check_prereqs
rc=0
run_test test_create
run_test test_create_empty
run_test test_create_read
run_test test_delete
run_test test_zero_size_delete
run_test test_open_unlink
run_test test_valid_filenames
run_test test_invalid_filenames
exit $rc

View File

@@ -0,0 +1,63 @@
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
const char *path;
char buf[5];
int fd, rc;
if (argc < 2) {
fprintf(stderr, "usage: %s <path>\n", argv[0]);
return EXIT_FAILURE;
}
path = argv[1];
/* attributes: EFI_VARIABLE_NON_VOLATILE |
* EFI_VARIABLE_BOOTSERVICE_ACCESS |
* EFI_VARIABLE_RUNTIME_ACCESS
*/
*(uint32_t *)buf = 0x7;
buf[4] = 0;
/* create a test variable */
fd = open(path, O_WRONLY | O_CREAT);
if (fd < 0) {
perror("open(O_WRONLY)");
return EXIT_FAILURE;
}
rc = write(fd, buf, sizeof(buf));
if (rc != sizeof(buf)) {
perror("write");
return EXIT_FAILURE;
}
close(fd);
fd = open(path, O_RDONLY);
if (fd < 0) {
perror("open");
return EXIT_FAILURE;
}
if (unlink(path) < 0) {
perror("unlink");
return EXIT_FAILURE;
}
rc = read(fd, buf, sizeof(buf));
if (rc > 0) {
fprintf(stderr, "reading from an unlinked variable "
"shouldn't be possible\n");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,25 @@
uname_M := $(shell uname -m 2>/dev/null || echo not)
ARCH ?= $(shell echo $(uname_M) | sed -e s/i.86/i386/)
ifeq ($(ARCH),i386)
ARCH := X86
CFLAGS := -DCONFIG_X86_32 -D__i386__
endif
ifeq ($(ARCH),x86_64)
ARCH := X86
CFLAGS := -DCONFIG_X86_64 -D__x86_64__
endif
CFLAGS += -I../../../../usr/include/
all:
ifeq ($(ARCH),X86)
gcc $(CFLAGS) msgque.c -o msgque_test
else
echo "Not an x86 target, can't build msgque selftest"
endif
run_tests: all
./msgque_test
clean:
rm -fr ./msgque_test

View File

@@ -0,0 +1,246 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <linux/msg.h>
#include <fcntl.h>
#define MAX_MSG_SIZE 32
struct msg1 {
int msize;
long mtype;
char mtext[MAX_MSG_SIZE];
};
#define TEST_STRING "Test sysv5 msg"
#define MSG_TYPE 1
#define ANOTHER_TEST_STRING "Yet another test sysv5 msg"
#define ANOTHER_MSG_TYPE 26538
struct msgque_data {
key_t key;
int msq_id;
int qbytes;
int qnum;
int mode;
struct msg1 *messages;
};
int restore_queue(struct msgque_data *msgque)
{
int fd, ret, id, i;
char buf[32];
fd = open("/proc/sys/kernel/msg_next_id", O_WRONLY);
if (fd == -1) {
printf("Failed to open /proc/sys/kernel/msg_next_id\n");
return -errno;
}
sprintf(buf, "%d", msgque->msq_id);
ret = write(fd, buf, strlen(buf));
if (ret != strlen(buf)) {
printf("Failed to write to /proc/sys/kernel/msg_next_id\n");
return -errno;
}
id = msgget(msgque->key, msgque->mode | IPC_CREAT | IPC_EXCL);
if (id == -1) {
printf("Failed to create queue\n");
return -errno;
}
if (id != msgque->msq_id) {
printf("Restored queue has wrong id (%d instead of %d)\n",
id, msgque->msq_id);
ret = -EFAULT;
goto destroy;
}
for (i = 0; i < msgque->qnum; i++) {
if (msgsnd(msgque->msq_id, &msgque->messages[i].mtype,
msgque->messages[i].msize, IPC_NOWAIT) != 0) {
printf("msgsnd failed (%m)\n");
ret = -errno;
goto destroy;
};
}
return 0;
destroy:
if (msgctl(id, IPC_RMID, 0))
printf("Failed to destroy queue: %d\n", -errno);
return ret;
}
int check_and_destroy_queue(struct msgque_data *msgque)
{
struct msg1 message;
int cnt = 0, ret;
while (1) {
ret = msgrcv(msgque->msq_id, &message.mtype, MAX_MSG_SIZE,
0, IPC_NOWAIT);
if (ret < 0) {
if (errno == ENOMSG)
break;
printf("Failed to read IPC message: %m\n");
ret = -errno;
goto err;
}
if (ret != msgque->messages[cnt].msize) {
printf("Wrong message size: %d (expected %d)\n", ret,
msgque->messages[cnt].msize);
ret = -EINVAL;
goto err;
}
if (message.mtype != msgque->messages[cnt].mtype) {
printf("Wrong message type\n");
ret = -EINVAL;
goto err;
}
if (memcmp(message.mtext, msgque->messages[cnt].mtext, ret)) {
printf("Wrong message content\n");
ret = -EINVAL;
goto err;
}
cnt++;
}
if (cnt != msgque->qnum) {
printf("Wrong message number\n");
ret = -EINVAL;
goto err;
}
ret = 0;
err:
if (msgctl(msgque->msq_id, IPC_RMID, 0)) {
printf("Failed to destroy queue: %d\n", -errno);
return -errno;
}
return ret;
}
int dump_queue(struct msgque_data *msgque)
{
struct msqid64_ds ds;
int kern_id;
int i, ret;
for (kern_id = 0; kern_id < 256; kern_id++) {
ret = msgctl(kern_id, MSG_STAT, &ds);
if (ret < 0) {
if (errno == -EINVAL)
continue;
printf("Failed to get stats for IPC queue with id %d\n",
kern_id);
return -errno;
}
if (ret == msgque->msq_id)
break;
}
msgque->messages = malloc(sizeof(struct msg1) * ds.msg_qnum);
if (msgque->messages == NULL) {
printf("Failed to get stats for IPC queue\n");
return -ENOMEM;
}
msgque->qnum = ds.msg_qnum;
msgque->mode = ds.msg_perm.mode;
msgque->qbytes = ds.msg_qbytes;
for (i = 0; i < msgque->qnum; i++) {
ret = msgrcv(msgque->msq_id, &msgque->messages[i].mtype,
MAX_MSG_SIZE, i, IPC_NOWAIT | MSG_COPY);
if (ret < 0) {
printf("Failed to copy IPC message: %m (%d)\n", errno);
return -errno;
}
msgque->messages[i].msize = ret;
}
return 0;
}
int fill_msgque(struct msgque_data *msgque)
{
struct msg1 msgbuf;
msgbuf.mtype = MSG_TYPE;
memcpy(msgbuf.mtext, TEST_STRING, sizeof(TEST_STRING));
if (msgsnd(msgque->msq_id, &msgbuf.mtype, sizeof(TEST_STRING),
IPC_NOWAIT) != 0) {
printf("First message send failed (%m)\n");
return -errno;
};
msgbuf.mtype = ANOTHER_MSG_TYPE;
memcpy(msgbuf.mtext, ANOTHER_TEST_STRING, sizeof(ANOTHER_TEST_STRING));
if (msgsnd(msgque->msq_id, &msgbuf.mtype, sizeof(ANOTHER_TEST_STRING),
IPC_NOWAIT) != 0) {
printf("Second message send failed (%m)\n");
return -errno;
};
return 0;
}
int main(int argc, char **argv)
{
int msg, pid, err;
struct msgque_data msgque;
msgque.key = ftok(argv[0], 822155650);
if (msgque.key == -1) {
printf("Can't make key\n");
return -errno;
}
msgque.msq_id = msgget(msgque.key, IPC_CREAT | IPC_EXCL | 0666);
if (msgque.msq_id == -1) {
printf("Can't create queue\n");
goto err_out;
}
err = fill_msgque(&msgque);
if (err) {
printf("Failed to fill queue\n");
goto err_destroy;
}
err = dump_queue(&msgque);
if (err) {
printf("Failed to dump queue\n");
goto err_destroy;
}
err = check_and_destroy_queue(&msgque);
if (err) {
printf("Failed to check and destroy queue\n");
goto err_out;
}
err = restore_queue(&msgque);
if (err) {
printf("Failed to restore queue\n");
goto err_destroy;
}
err = check_and_destroy_queue(&msgque);
if (err) {
printf("Failed to test queue\n");
goto err_out;
}
return 0;
err_destroy:
if (msgctl(msgque.msq_id, IPC_RMID, 0)) {
printf("Failed to destroy queue: %d\n", -errno);
return -errno;
}
err_out:
return err;
}

View File

@@ -0,0 +1,29 @@
uname_M := $(shell uname -m 2>/dev/null || echo not)
ARCH ?= $(shell echo $(uname_M) | sed -e s/i.86/i386/)
ifeq ($(ARCH),i386)
ARCH := X86
CFLAGS := -DCONFIG_X86_32 -D__i386__
endif
ifeq ($(ARCH),x86_64)
ARCH := X86
CFLAGS := -DCONFIG_X86_64 -D__x86_64__
endif
CFLAGS += -I../../../../arch/x86/include/generated/
CFLAGS += -I../../../../include/
CFLAGS += -I../../../../usr/include/
CFLAGS += -I../../../../arch/x86/include/
all:
ifeq ($(ARCH),X86)
gcc $(CFLAGS) kcmp_test.c -o kcmp_test
else
echo "Not an x86 target, can't build kcmp selftest"
endif
run_tests: all
@./kcmp_test || echo "kcmp_test: [FAIL]"
clean:
rm -fr ./run_test
rm -fr ./test-file

View File

@@ -0,0 +1,96 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <limits.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <linux/unistd.h>
#include <linux/kcmp.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
static long sys_kcmp(int pid1, int pid2, int type, int fd1, int fd2)
{
return syscall(__NR_kcmp, pid1, pid2, type, fd1, fd2);
}
int main(int argc, char **argv)
{
const char kpath[] = "kcmp-test-file";
int pid1, pid2;
int fd1, fd2;
int status;
fd1 = open(kpath, O_RDWR | O_CREAT | O_TRUNC, 0644);
pid1 = getpid();
if (fd1 < 0) {
perror("Can't create file");
exit(1);
}
pid2 = fork();
if (pid2 < 0) {
perror("fork failed");
exit(1);
}
if (!pid2) {
int pid2 = getpid();
int ret;
fd2 = open(kpath, O_RDWR, 0644);
if (fd2 < 0) {
perror("Can't open file");
exit(1);
}
/* An example of output and arguments */
printf("pid1: %6d pid2: %6d FD: %2ld FILES: %2ld VM: %2ld "
"FS: %2ld SIGHAND: %2ld IO: %2ld SYSVSEM: %2ld "
"INV: %2ld\n",
pid1, pid2,
sys_kcmp(pid1, pid2, KCMP_FILE, fd1, fd2),
sys_kcmp(pid1, pid2, KCMP_FILES, 0, 0),
sys_kcmp(pid1, pid2, KCMP_VM, 0, 0),
sys_kcmp(pid1, pid2, KCMP_FS, 0, 0),
sys_kcmp(pid1, pid2, KCMP_SIGHAND, 0, 0),
sys_kcmp(pid1, pid2, KCMP_IO, 0, 0),
sys_kcmp(pid1, pid2, KCMP_SYSVSEM, 0, 0),
/* This one should fail */
sys_kcmp(pid1, pid2, KCMP_TYPES + 1, 0, 0));
/* This one should return same fd */
ret = sys_kcmp(pid1, pid2, KCMP_FILE, fd1, fd1);
if (ret) {
printf("FAIL: 0 expected but %d returned (%s)\n",
ret, strerror(errno));
ret = -1;
} else
printf("PASS: 0 returned as expected\n");
/* Compare with self */
ret = sys_kcmp(pid1, pid1, KCMP_VM, 0, 0);
if (ret) {
printf("FAIL: 0 expected but %li returned (%s)\n",
ret, strerror(errno));
ret = -1;
} else
printf("PASS: 0 returned as expected\n");
exit(ret);
}
waitpid(pid2, &status, P_ALL);
return 0;
}

View File

@@ -0,0 +1,6 @@
all:
run_tests:
@./on-off-test.sh || echo "memory-hotplug selftests: [FAIL]"
clean:

View File

@@ -0,0 +1,230 @@
#!/bin/bash
SYSFS=
prerequisite()
{
msg="skip all tests:"
if [ $UID != 0 ]; then
echo $msg must be run as root >&2
exit 0
fi
SYSFS=`mount -t sysfs | head -1 | awk '{ print $3 }'`
if [ ! -d "$SYSFS" ]; then
echo $msg sysfs is not mounted >&2
exit 0
fi
if ! ls $SYSFS/devices/system/memory/memory* > /dev/null 2>&1; then
echo $msg memory hotplug is not supported >&2
exit 0
fi
}
#
# list all hot-pluggable memory
#
hotpluggable_memory()
{
local state=${1:-.\*}
for memory in $SYSFS/devices/system/memory/memory*; do
if grep -q 1 $memory/removable &&
grep -q $state $memory/state; then
echo ${memory##/*/memory}
fi
done
}
hotplaggable_offline_memory()
{
hotpluggable_memory offline
}
hotpluggable_online_memory()
{
hotpluggable_memory online
}
memory_is_online()
{
grep -q online $SYSFS/devices/system/memory/memory$1/state
}
memory_is_offline()
{
grep -q offline $SYSFS/devices/system/memory/memory$1/state
}
online_memory()
{
echo online > $SYSFS/devices/system/memory/memory$1/state
}
offline_memory()
{
echo offline > $SYSFS/devices/system/memory/memory$1/state
}
online_memory_expect_success()
{
local memory=$1
if ! online_memory $memory; then
echo $FUNCNAME $memory: unexpected fail >&2
elif ! memory_is_online $memory; then
echo $FUNCNAME $memory: unexpected offline >&2
fi
}
online_memory_expect_fail()
{
local memory=$1
if online_memory $memory 2> /dev/null; then
echo $FUNCNAME $memory: unexpected success >&2
elif ! memory_is_offline $memory; then
echo $FUNCNAME $memory: unexpected online >&2
fi
}
offline_memory_expect_success()
{
local memory=$1
if ! offline_memory $memory; then
echo $FUNCNAME $memory: unexpected fail >&2
elif ! memory_is_offline $memory; then
echo $FUNCNAME $memory: unexpected offline >&2
fi
}
offline_memory_expect_fail()
{
local memory=$1
if offline_memory $memory 2> /dev/null; then
echo $FUNCNAME $memory: unexpected success >&2
elif ! memory_is_online $memory; then
echo $FUNCNAME $memory: unexpected offline >&2
fi
}
error=-12
priority=0
ratio=10
while getopts e:hp:r: opt; do
case $opt in
e)
error=$OPTARG
;;
h)
echo "Usage $0 [ -e errno ] [ -p notifier-priority ] [ -r percent-of-memory-to-offline ]"
exit
;;
p)
priority=$OPTARG
;;
r)
ratio=$OPTARG
;;
esac
done
if ! [ "$error" -ge -4095 -a "$error" -lt 0 ]; then
echo "error code must be -4095 <= errno < 0" >&2
exit 1
fi
prerequisite
#
# Online all hot-pluggable memory
#
for memory in `hotplaggable_offline_memory`; do
online_memory_expect_success $memory
done
#
# Offline $ratio percent of hot-pluggable memory
#
for memory in `hotpluggable_online_memory`; do
if [ $((RANDOM % 100)) -lt $ratio ]; then
offline_memory_expect_success $memory
fi
done
#
# Online all hot-pluggable memory again
#
for memory in `hotplaggable_offline_memory`; do
online_memory_expect_success $memory
done
#
# Test with memory notifier error injection
#
DEBUGFS=`mount -t debugfs | head -1 | awk '{ print $3 }'`
NOTIFIER_ERR_INJECT_DIR=$DEBUGFS/notifier-error-inject/memory
prerequisite_extra()
{
msg="skip extra tests:"
/sbin/modprobe -q -r memory-notifier-error-inject
/sbin/modprobe -q memory-notifier-error-inject priority=$priority
if [ ! -d "$DEBUGFS" ]; then
echo $msg debugfs is not mounted >&2
exit 0
fi
if [ ! -d $NOTIFIER_ERR_INJECT_DIR ]; then
echo $msg memory-notifier-error-inject module is not available >&2
exit 0
fi
}
prerequisite_extra
#
# Offline $ratio percent of hot-pluggable memory
#
echo 0 > $NOTIFIER_ERR_INJECT_DIR/actions/MEM_GOING_OFFLINE/error
for memory in `hotpluggable_online_memory`; do
if [ $((RANDOM % 100)) -lt $ratio ]; then
offline_memory_expect_success $memory
fi
done
#
# Test memory hot-add error handling (offline => online)
#
echo $error > $NOTIFIER_ERR_INJECT_DIR/actions/MEM_GOING_ONLINE/error
for memory in `hotplaggable_offline_memory`; do
online_memory_expect_fail $memory
done
#
# Online all hot-pluggable memory
#
echo 0 > $NOTIFIER_ERR_INJECT_DIR/actions/MEM_GOING_ONLINE/error
for memory in `hotplaggable_offline_memory`; do
online_memory_expect_success $memory
done
#
# Test memory hot-remove error handling (online => offline)
#
echo $error > $NOTIFIER_ERR_INJECT_DIR/actions/MEM_GOING_OFFLINE/error
for memory in `hotpluggable_online_memory`; do
offline_memory_expect_fail $memory
done
echo 0 > $NOTIFIER_ERR_INJECT_DIR/actions/MEM_GOING_OFFLINE/error
/sbin/modprobe -q -r memory-notifier-error-inject

View File

@@ -0,0 +1,2 @@
mq_open_tests
mq_perf_tests

View File

@@ -0,0 +1,10 @@
all:
gcc -O2 -lrt mq_open_tests.c -o mq_open_tests
gcc -O2 -lrt -lpthread -lpopt -o mq_perf_tests mq_perf_tests.c
run_tests:
@./mq_open_tests /test1 || echo "mq_open_tests: [FAIL]"
@./mq_perf_tests || echo "mq_perf_tests: [FAIL]"
clean:
rm -f mq_open_tests mq_perf_tests

View File

@@ -0,0 +1,492 @@
/*
* This application is Copyright 2012 Red Hat, Inc.
* Doug Ledford <dledford@redhat.com>
*
* mq_open_tests 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, version 3.
*
* mq_open_tests 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.
*
* For the full text of the license, see <http://www.gnu.org/licenses/>.
*
* mq_open_tests.c
* Tests the various situations that should either succeed or fail to
* open a posix message queue and then reports whether or not they
* did as they were supposed to.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <mqueue.h>
static char *usage =
"Usage:\n"
" %s path\n"
"\n"
" path Path name of the message queue to create\n"
"\n"
" Note: this program must be run as root in order to enable all tests\n"
"\n";
char *DEF_MSGS = "/proc/sys/fs/mqueue/msg_default";
char *DEF_MSGSIZE = "/proc/sys/fs/mqueue/msgsize_default";
char *MAX_MSGS = "/proc/sys/fs/mqueue/msg_max";
char *MAX_MSGSIZE = "/proc/sys/fs/mqueue/msgsize_max";
int default_settings;
struct rlimit saved_limits, cur_limits;
int saved_def_msgs, saved_def_msgsize, saved_max_msgs, saved_max_msgsize;
int cur_def_msgs, cur_def_msgsize, cur_max_msgs, cur_max_msgsize;
FILE *def_msgs, *def_msgsize, *max_msgs, *max_msgsize;
char *queue_path;
mqd_t queue = -1;
static inline void __set(FILE *stream, int value, char *err_msg);
void shutdown(int exit_val, char *err_cause, int line_no);
static inline int get(FILE *stream);
static inline void set(FILE *stream, int value);
static inline void getr(int type, struct rlimit *rlim);
static inline void setr(int type, struct rlimit *rlim);
void validate_current_settings();
static inline void test_queue(struct mq_attr *attr, struct mq_attr *result);
static inline int test_queue_fail(struct mq_attr *attr, struct mq_attr *result);
static inline void __set(FILE *stream, int value, char *err_msg)
{
rewind(stream);
if (fprintf(stream, "%d", value) < 0)
perror(err_msg);
}
void shutdown(int exit_val, char *err_cause, int line_no)
{
static int in_shutdown = 0;
/* In case we get called recursively by a set() call below */
if (in_shutdown++)
return;
seteuid(0);
if (queue != -1)
if (mq_close(queue))
perror("mq_close() during shutdown");
if (queue_path)
/*
* Be silent if this fails, if we cleaned up already it's
* expected to fail
*/
mq_unlink(queue_path);
if (default_settings) {
if (saved_def_msgs)
__set(def_msgs, saved_def_msgs,
"failed to restore saved_def_msgs");
if (saved_def_msgsize)
__set(def_msgsize, saved_def_msgsize,
"failed to restore saved_def_msgsize");
}
if (saved_max_msgs)
__set(max_msgs, saved_max_msgs,
"failed to restore saved_max_msgs");
if (saved_max_msgsize)
__set(max_msgsize, saved_max_msgsize,
"failed to restore saved_max_msgsize");
if (exit_val)
error(exit_val, errno, "%s at %d", err_cause, line_no);
exit(0);
}
static inline int get(FILE *stream)
{
int value;
rewind(stream);
if (fscanf(stream, "%d", &value) != 1)
shutdown(4, "Error reading /proc entry", __LINE__ - 1);
return value;
}
static inline void set(FILE *stream, int value)
{
int new_value;
rewind(stream);
if (fprintf(stream, "%d", value) < 0)
return shutdown(5, "Failed writing to /proc file",
__LINE__ - 1);
new_value = get(stream);
if (new_value != value)
return shutdown(5, "We didn't get what we wrote to /proc back",
__LINE__ - 1);
}
static inline void getr(int type, struct rlimit *rlim)
{
if (getrlimit(type, rlim))
shutdown(6, "getrlimit()", __LINE__ - 1);
}
static inline void setr(int type, struct rlimit *rlim)
{
if (setrlimit(type, rlim))
shutdown(7, "setrlimit()", __LINE__ - 1);
}
void validate_current_settings()
{
int rlim_needed;
if (cur_limits.rlim_cur < 4096) {
printf("Current rlimit value for POSIX message queue bytes is "
"unreasonably low,\nincreasing.\n\n");
cur_limits.rlim_cur = 8192;
cur_limits.rlim_max = 16384;
setr(RLIMIT_MSGQUEUE, &cur_limits);
}
if (default_settings) {
rlim_needed = (cur_def_msgs + 1) * (cur_def_msgsize + 1 +
2 * sizeof(void *));
if (rlim_needed > cur_limits.rlim_cur) {
printf("Temporarily lowering default queue parameters "
"to something that will work\n"
"with the current rlimit values.\n\n");
set(def_msgs, 10);
cur_def_msgs = 10;
set(def_msgsize, 128);
cur_def_msgsize = 128;
}
} else {
rlim_needed = (cur_max_msgs + 1) * (cur_max_msgsize + 1 +
2 * sizeof(void *));
if (rlim_needed > cur_limits.rlim_cur) {
printf("Temporarily lowering maximum queue parameters "
"to something that will work\n"
"with the current rlimit values in case this is "
"a kernel that ties the default\n"
"queue parameters to the maximum queue "
"parameters.\n\n");
set(max_msgs, 10);
cur_max_msgs = 10;
set(max_msgsize, 128);
cur_max_msgsize = 128;
}
}
}
/*
* test_queue - Test opening a queue, shutdown if we fail. This should
* only be called in situations that should never fail. We clean up
* after ourselves and return the queue attributes in *result.
*/
static inline void test_queue(struct mq_attr *attr, struct mq_attr *result)
{
int flags = O_RDWR | O_EXCL | O_CREAT;
int perms = DEFFILEMODE;
if ((queue = mq_open(queue_path, flags, perms, attr)) == -1)
shutdown(1, "mq_open()", __LINE__);
if (mq_getattr(queue, result))
shutdown(1, "mq_getattr()", __LINE__);
if (mq_close(queue))
shutdown(1, "mq_close()", __LINE__);
queue = -1;
if (mq_unlink(queue_path))
shutdown(1, "mq_unlink()", __LINE__);
}
/*
* Same as test_queue above, but failure is not fatal.
* Returns:
* 0 - Failed to create a queue
* 1 - Created a queue, attributes in *result
*/
static inline int test_queue_fail(struct mq_attr *attr, struct mq_attr *result)
{
int flags = O_RDWR | O_EXCL | O_CREAT;
int perms = DEFFILEMODE;
if ((queue = mq_open(queue_path, flags, perms, attr)) == -1)
return 0;
if (mq_getattr(queue, result))
shutdown(1, "mq_getattr()", __LINE__);
if (mq_close(queue))
shutdown(1, "mq_close()", __LINE__);
queue = -1;
if (mq_unlink(queue_path))
shutdown(1, "mq_unlink()", __LINE__);
return 1;
}
int main(int argc, char *argv[])
{
struct mq_attr attr, result;
if (argc != 2) {
fprintf(stderr, "Must pass a valid queue name\n\n");
fprintf(stderr, usage, argv[0]);
exit(1);
}
/*
* Although we can create a msg queue with a non-absolute path name,
* unlink will fail. So, if the name doesn't start with a /, add one
* when we save it.
*/
if (*argv[1] == '/')
queue_path = strdup(argv[1]);
else {
queue_path = malloc(strlen(argv[1]) + 2);
if (!queue_path) {
perror("malloc()");
exit(1);
}
queue_path[0] = '/';
queue_path[1] = 0;
strcat(queue_path, argv[1]);
}
if (getuid() != 0) {
fprintf(stderr, "Not running as root, but almost all tests "
"require root in order to modify\nsystem settings. "
"Exiting.\n");
exit(1);
}
/* Find out what files there are for us to make tweaks in */
def_msgs = fopen(DEF_MSGS, "r+");
def_msgsize = fopen(DEF_MSGSIZE, "r+");
max_msgs = fopen(MAX_MSGS, "r+");
max_msgsize = fopen(MAX_MSGSIZE, "r+");
if (!max_msgs)
shutdown(2, "Failed to open msg_max", __LINE__);
if (!max_msgsize)
shutdown(2, "Failed to open msgsize_max", __LINE__);
if (def_msgs || def_msgsize)
default_settings = 1;
/* Load up the current system values for everything we can */
getr(RLIMIT_MSGQUEUE, &saved_limits);
cur_limits = saved_limits;
if (default_settings) {
saved_def_msgs = cur_def_msgs = get(def_msgs);
saved_def_msgsize = cur_def_msgsize = get(def_msgsize);
}
saved_max_msgs = cur_max_msgs = get(max_msgs);
saved_max_msgsize = cur_max_msgsize = get(max_msgsize);
/* Tell the user our initial state */
printf("\nInitial system state:\n");
printf("\tUsing queue path:\t\t%s\n", queue_path);
printf("\tRLIMIT_MSGQUEUE(soft):\t\t%d\n", saved_limits.rlim_cur);
printf("\tRLIMIT_MSGQUEUE(hard):\t\t%d\n", saved_limits.rlim_max);
printf("\tMaximum Message Size:\t\t%d\n", saved_max_msgsize);
printf("\tMaximum Queue Size:\t\t%d\n", saved_max_msgs);
if (default_settings) {
printf("\tDefault Message Size:\t\t%d\n", saved_def_msgsize);
printf("\tDefault Queue Size:\t\t%d\n", saved_def_msgs);
} else {
printf("\tDefault Message Size:\t\tNot Supported\n");
printf("\tDefault Queue Size:\t\tNot Supported\n");
}
printf("\n");
validate_current_settings();
printf("Adjusted system state for testing:\n");
printf("\tRLIMIT_MSGQUEUE(soft):\t\t%d\n", cur_limits.rlim_cur);
printf("\tRLIMIT_MSGQUEUE(hard):\t\t%d\n", cur_limits.rlim_max);
printf("\tMaximum Message Size:\t\t%d\n", cur_max_msgsize);
printf("\tMaximum Queue Size:\t\t%d\n", cur_max_msgs);
if (default_settings) {
printf("\tDefault Message Size:\t\t%d\n", cur_def_msgsize);
printf("\tDefault Queue Size:\t\t%d\n", cur_def_msgs);
}
printf("\n\nTest series 1, behavior when no attr struct "
"passed to mq_open:\n");
if (!default_settings) {
test_queue(NULL, &result);
printf("Given sane system settings, mq_open without an attr "
"struct succeeds:\tPASS\n");
if (result.mq_maxmsg != cur_max_msgs ||
result.mq_msgsize != cur_max_msgsize) {
printf("Kernel does not support setting the default "
"mq attributes,\nbut also doesn't tie the "
"defaults to the maximums:\t\t\tPASS\n");
} else {
set(max_msgs, ++cur_max_msgs);
set(max_msgsize, ++cur_max_msgsize);
test_queue(NULL, &result);
if (result.mq_maxmsg == cur_max_msgs &&
result.mq_msgsize == cur_max_msgsize)
printf("Kernel does not support setting the "
"default mq attributes and\n"
"also ties system wide defaults to "
"the system wide maximums:\t\t"
"FAIL\n");
else
printf("Kernel does not support setting the "
"default mq attributes,\n"
"but also doesn't tie the defaults to "
"the maximums:\t\t\tPASS\n");
}
} else {
printf("Kernel supports setting defaults separately from "
"maximums:\t\tPASS\n");
/*
* While we are here, go ahead and test that the kernel
* properly follows the default settings
*/
test_queue(NULL, &result);
printf("Given sane values, mq_open without an attr struct "
"succeeds:\t\tPASS\n");
if (result.mq_maxmsg != cur_def_msgs ||
result.mq_msgsize != cur_def_msgsize)
printf("Kernel supports setting defaults, but does "
"not actually honor them:\tFAIL\n\n");
else {
set(def_msgs, ++cur_def_msgs);
set(def_msgsize, ++cur_def_msgsize);
/* In case max was the same as the default */
set(max_msgs, ++cur_max_msgs);
set(max_msgsize, ++cur_max_msgsize);
test_queue(NULL, &result);
if (result.mq_maxmsg != cur_def_msgs ||
result.mq_msgsize != cur_def_msgsize)
printf("Kernel supports setting defaults, but "
"does not actually honor them:\t"
"FAIL\n");
else
printf("Kernel properly honors default setting "
"knobs:\t\t\t\tPASS\n");
}
set(def_msgs, cur_max_msgs + 1);
cur_def_msgs = cur_max_msgs + 1;
set(def_msgsize, cur_max_msgsize + 1);
cur_def_msgsize = cur_max_msgsize + 1;
if (cur_def_msgs * (cur_def_msgsize + 2 * sizeof(void *)) >=
cur_limits.rlim_cur) {
cur_limits.rlim_cur = (cur_def_msgs + 2) *
(cur_def_msgsize + 2 * sizeof(void *));
cur_limits.rlim_max = 2 * cur_limits.rlim_cur;
setr(RLIMIT_MSGQUEUE, &cur_limits);
}
if (test_queue_fail(NULL, &result)) {
if (result.mq_maxmsg == cur_max_msgs &&
result.mq_msgsize == cur_max_msgsize)
printf("Kernel properly limits default values "
"to lesser of default/max:\t\tPASS\n");
else
printf("Kernel does not properly set default "
"queue parameters when\ndefaults > "
"max:\t\t\t\t\t\t\t\tFAIL\n");
} else
printf("Kernel fails to open mq because defaults are "
"greater than maximums:\tFAIL\n");
set(def_msgs, --cur_def_msgs);
set(def_msgsize, --cur_def_msgsize);
cur_limits.rlim_cur = cur_limits.rlim_max = cur_def_msgs *
cur_def_msgsize;
setr(RLIMIT_MSGQUEUE, &cur_limits);
if (test_queue_fail(NULL, &result))
printf("Kernel creates queue even though defaults "
"would exceed\nrlimit setting:"
"\t\t\t\t\t\t\t\tFAIL\n");
else
printf("Kernel properly fails to create queue when "
"defaults would\nexceed rlimit:"
"\t\t\t\t\t\t\t\tPASS\n");
}
/*
* Test #2 - open with an attr struct that exceeds rlimit
*/
printf("\n\nTest series 2, behavior when attr struct is "
"passed to mq_open:\n");
cur_max_msgs = 32;
cur_max_msgsize = cur_limits.rlim_max >> 4;
set(max_msgs, cur_max_msgs);
set(max_msgsize, cur_max_msgsize);
attr.mq_maxmsg = cur_max_msgs;
attr.mq_msgsize = cur_max_msgsize;
if (test_queue_fail(&attr, &result))
printf("Queue open in excess of rlimit max when euid = 0 "
"succeeded:\t\tFAIL\n");
else
printf("Queue open in excess of rlimit max when euid = 0 "
"failed:\t\tPASS\n");
attr.mq_maxmsg = cur_max_msgs + 1;
attr.mq_msgsize = 10;
if (test_queue_fail(&attr, &result))
printf("Queue open with mq_maxmsg > limit when euid = 0 "
"succeeded:\t\tPASS\n");
else
printf("Queue open with mq_maxmsg > limit when euid = 0 "
"failed:\t\tFAIL\n");
attr.mq_maxmsg = 1;
attr.mq_msgsize = cur_max_msgsize + 1;
if (test_queue_fail(&attr, &result))
printf("Queue open with mq_msgsize > limit when euid = 0 "
"succeeded:\t\tPASS\n");
else
printf("Queue open with mq_msgsize > limit when euid = 0 "
"failed:\t\tFAIL\n");
attr.mq_maxmsg = 65536;
attr.mq_msgsize = 65536;
if (test_queue_fail(&attr, &result))
printf("Queue open with total size > 2GB when euid = 0 "
"succeeded:\t\tFAIL\n");
else
printf("Queue open with total size > 2GB when euid = 0 "
"failed:\t\t\tPASS\n");
seteuid(99);
attr.mq_maxmsg = cur_max_msgs;
attr.mq_msgsize = cur_max_msgsize;
if (test_queue_fail(&attr, &result))
printf("Queue open in excess of rlimit max when euid = 99 "
"succeeded:\t\tFAIL\n");
else
printf("Queue open in excess of rlimit max when euid = 99 "
"failed:\t\tPASS\n");
attr.mq_maxmsg = cur_max_msgs + 1;
attr.mq_msgsize = 10;
if (test_queue_fail(&attr, &result))
printf("Queue open with mq_maxmsg > limit when euid = 99 "
"succeeded:\t\tFAIL\n");
else
printf("Queue open with mq_maxmsg > limit when euid = 99 "
"failed:\t\tPASS\n");
attr.mq_maxmsg = 1;
attr.mq_msgsize = cur_max_msgsize + 1;
if (test_queue_fail(&attr, &result))
printf("Queue open with mq_msgsize > limit when euid = 99 "
"succeeded:\t\tFAIL\n");
else
printf("Queue open with mq_msgsize > limit when euid = 99 "
"failed:\t\tPASS\n");
attr.mq_maxmsg = 65536;
attr.mq_msgsize = 65536;
if (test_queue_fail(&attr, &result))
printf("Queue open with total size > 2GB when euid = 99 "
"succeeded:\t\tFAIL\n");
else
printf("Queue open with total size > 2GB when euid = 99 "
"failed:\t\t\tPASS\n");
shutdown(0,"",0);
}

View File

@@ -0,0 +1,741 @@
/*
* This application is Copyright 2012 Red Hat, Inc.
* Doug Ledford <dledford@redhat.com>
*
* mq_perf_tests 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, version 3.
*
* mq_perf_tests 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.
*
* For the full text of the license, see <http://www.gnu.org/licenses/>.
*
* mq_perf_tests.c
* Tests various types of message queue workloads, concentrating on those
* situations that invole large message sizes, large message queue depths,
* or both, and reports back useful metrics about kernel message queue
* performance.
*
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include <signal.h>
#include <pthread.h>
#include <sched.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <popt.h>
static char *usage =
"Usage:\n"
" %s [-c #[,#..] -f] path\n"
"\n"
" -c # Skip most tests and go straight to a high queue depth test\n"
" and then run that test continuously (useful for running at\n"
" the same time as some other workload to see how much the\n"
" cache thrashing caused by adding messages to a very deep\n"
" queue impacts the performance of other programs). The number\n"
" indicates which CPU core we should bind the process to during\n"
" the run. If you have more than one physical CPU, then you\n"
" will need one copy per physical CPU package, and you should\n"
" specify the CPU cores to pin ourself to via a comma separated\n"
" list of CPU values.\n"
" -f Only usable with continuous mode. Pin ourself to the CPUs\n"
" as requested, then instead of looping doing a high mq\n"
" workload, just busy loop. This will allow us to lock up a\n"
" single CPU just like we normally would, but without actually\n"
" thrashing the CPU cache. This is to make it easier to get\n"
" comparable numbers from some other workload running on the\n"
" other CPUs. One set of numbers with # CPUs locked up running\n"
" an mq workload, and another set of numbers with those same\n"
" CPUs locked away from the test workload, but not doing\n"
" anything to trash the cache like the mq workload might.\n"
" path Path name of the message queue to create\n"
"\n"
" Note: this program must be run as root in order to enable all tests\n"
"\n";
char *MAX_MSGS = "/proc/sys/fs/mqueue/msg_max";
char *MAX_MSGSIZE = "/proc/sys/fs/mqueue/msgsize_max";
#define min(a, b) ((a) < (b) ? (a) : (b))
#define MAX_CPUS 64
char *cpu_option_string;
int cpus_to_pin[MAX_CPUS];
int num_cpus_to_pin;
pthread_t cpu_threads[MAX_CPUS];
pthread_t main_thread;
cpu_set_t *cpu_set;
int cpu_set_size;
int cpus_online;
#define MSG_SIZE 16
#define TEST1_LOOPS 10000000
#define TEST2_LOOPS 100000
int continuous_mode;
int continuous_mode_fake;
struct rlimit saved_limits, cur_limits;
int saved_max_msgs, saved_max_msgsize;
int cur_max_msgs, cur_max_msgsize;
FILE *max_msgs, *max_msgsize;
int cur_nice;
char *queue_path = "/mq_perf_tests";
mqd_t queue = -1;
struct mq_attr result;
int mq_prio_max;
const struct poptOption options[] = {
{
.longName = "continuous",
.shortName = 'c',
.argInfo = POPT_ARG_STRING,
.arg = &cpu_option_string,
.val = 'c',
.descrip = "Run continuous tests at a high queue depth in "
"order to test the effects of cache thrashing on "
"other tasks on the system. This test is intended "
"to be run on one core of each physical CPU while "
"some other CPU intensive task is run on all the other "
"cores of that same physical CPU and the other task "
"is timed. It is assumed that the process of adding "
"messages to the message queue in a tight loop will "
"impact that other task to some degree. Once the "
"tests are performed in this way, you should then "
"re-run the tests using fake mode in order to check "
"the difference in time required to perform the CPU "
"intensive task",
.argDescrip = "cpu[,cpu]",
},
{
.longName = "fake",
.shortName = 'f',
.argInfo = POPT_ARG_NONE,
.arg = &continuous_mode_fake,
.val = 0,
.descrip = "Tie up the CPUs that we would normally tie up in"
"continuous mode, but don't actually do any mq stuff, "
"just keep the CPU busy so it can't be used to process "
"system level tasks as this would free up resources on "
"the other CPU cores and skew the comparison between "
"the no-mqueue work and mqueue work tests",
.argDescrip = NULL,
},
{
.longName = "path",
.shortName = 'p',
.argInfo = POPT_ARG_STRING | POPT_ARGFLAG_SHOW_DEFAULT,
.arg = &queue_path,
.val = 'p',
.descrip = "The name of the path to use in the mqueue "
"filesystem for our tests",
.argDescrip = "pathname",
},
POPT_AUTOHELP
POPT_TABLEEND
};
static inline void __set(FILE *stream, int value, char *err_msg);
void shutdown(int exit_val, char *err_cause, int line_no);
void sig_action_SIGUSR1(int signum, siginfo_t *info, void *context);
void sig_action(int signum, siginfo_t *info, void *context);
static inline int get(FILE *stream);
static inline void set(FILE *stream, int value);
static inline int try_set(FILE *stream, int value);
static inline void getr(int type, struct rlimit *rlim);
static inline void setr(int type, struct rlimit *rlim);
static inline void open_queue(struct mq_attr *attr);
void increase_limits(void);
static inline void __set(FILE *stream, int value, char *err_msg)
{
rewind(stream);
if (fprintf(stream, "%d", value) < 0)
perror(err_msg);
}
void shutdown(int exit_val, char *err_cause, int line_no)
{
static int in_shutdown = 0;
int errno_at_shutdown = errno;
int i;
/* In case we get called by multiple threads or from an sighandler */
if (in_shutdown++)
return;
for (i = 0; i < num_cpus_to_pin; i++)
if (cpu_threads[i]) {
pthread_kill(cpu_threads[i], SIGUSR1);
pthread_join(cpu_threads[i], NULL);
}
if (queue != -1)
if (mq_close(queue))
perror("mq_close() during shutdown");
if (queue_path)
/*
* Be silent if this fails, if we cleaned up already it's
* expected to fail
*/
mq_unlink(queue_path);
if (saved_max_msgs)
__set(max_msgs, saved_max_msgs,
"failed to restore saved_max_msgs");
if (saved_max_msgsize)
__set(max_msgsize, saved_max_msgsize,
"failed to restore saved_max_msgsize");
if (exit_val)
error(exit_val, errno_at_shutdown, "%s at %d",
err_cause, line_no);
exit(0);
}
void sig_action_SIGUSR1(int signum, siginfo_t *info, void *context)
{
if (pthread_self() != main_thread)
pthread_exit(0);
else {
fprintf(stderr, "Caught signal %d in SIGUSR1 handler, "
"exiting\n", signum);
shutdown(0, "", 0);
fprintf(stderr, "\n\nReturned from shutdown?!?!\n\n");
exit(0);
}
}
void sig_action(int signum, siginfo_t *info, void *context)
{
if (pthread_self() != main_thread)
pthread_kill(main_thread, signum);
else {
fprintf(stderr, "Caught signal %d, exiting\n", signum);
shutdown(0, "", 0);
fprintf(stderr, "\n\nReturned from shutdown?!?!\n\n");
exit(0);
}
}
static inline int get(FILE *stream)
{
int value;
rewind(stream);
if (fscanf(stream, "%d", &value) != 1)
shutdown(4, "Error reading /proc entry", __LINE__);
return value;
}
static inline void set(FILE *stream, int value)
{
int new_value;
rewind(stream);
if (fprintf(stream, "%d", value) < 0)
return shutdown(5, "Failed writing to /proc file", __LINE__);
new_value = get(stream);
if (new_value != value)
return shutdown(5, "We didn't get what we wrote to /proc back",
__LINE__);
}
static inline int try_set(FILE *stream, int value)
{
int new_value;
rewind(stream);
fprintf(stream, "%d", value);
new_value = get(stream);
return new_value == value;
}
static inline void getr(int type, struct rlimit *rlim)
{
if (getrlimit(type, rlim))
shutdown(6, "getrlimit()", __LINE__);
}
static inline void setr(int type, struct rlimit *rlim)
{
if (setrlimit(type, rlim))
shutdown(7, "setrlimit()", __LINE__);
}
/**
* open_queue - open the global queue for testing
* @attr - An attr struct specifying the desired queue traits
* @result - An attr struct that lists the actual traits the queue has
*
* This open is not allowed to fail, failure will result in an orderly
* shutdown of the program. The global queue_path is used to set what
* queue to open, the queue descriptor is saved in the global queue
* variable.
*/
static inline void open_queue(struct mq_attr *attr)
{
int flags = O_RDWR | O_EXCL | O_CREAT | O_NONBLOCK;
int perms = DEFFILEMODE;
queue = mq_open(queue_path, flags, perms, attr);
if (queue == -1)
shutdown(1, "mq_open()", __LINE__);
if (mq_getattr(queue, &result))
shutdown(1, "mq_getattr()", __LINE__);
printf("\n\tQueue %s created:\n", queue_path);
printf("\t\tmq_flags:\t\t\t%s\n", result.mq_flags & O_NONBLOCK ?
"O_NONBLOCK" : "(null)");
printf("\t\tmq_maxmsg:\t\t\t%d\n", result.mq_maxmsg);
printf("\t\tmq_msgsize:\t\t\t%d\n", result.mq_msgsize);
printf("\t\tmq_curmsgs:\t\t\t%d\n", result.mq_curmsgs);
}
void *fake_cont_thread(void *arg)
{
int i;
for (i = 0; i < num_cpus_to_pin; i++)
if (cpu_threads[i] == pthread_self())
break;
printf("\tStarted fake continuous mode thread %d on CPU %d\n", i,
cpus_to_pin[i]);
while (1)
;
}
void *cont_thread(void *arg)
{
char buff[MSG_SIZE];
int i, priority;
for (i = 0; i < num_cpus_to_pin; i++)
if (cpu_threads[i] == pthread_self())
break;
printf("\tStarted continuous mode thread %d on CPU %d\n", i,
cpus_to_pin[i]);
while (1) {
while (mq_send(queue, buff, sizeof(buff), 0) == 0)
;
mq_receive(queue, buff, sizeof(buff), &priority);
}
}
#define drain_queue() \
while (mq_receive(queue, buff, MSG_SIZE, &prio_in) == MSG_SIZE)
#define do_untimed_send() \
do { \
if (mq_send(queue, buff, MSG_SIZE, prio_out)) \
shutdown(3, "Test send failure", __LINE__); \
} while (0)
#define do_send_recv() \
do { \
clock_gettime(clock, &start); \
if (mq_send(queue, buff, MSG_SIZE, prio_out)) \
shutdown(3, "Test send failure", __LINE__); \
clock_gettime(clock, &middle); \
if (mq_receive(queue, buff, MSG_SIZE, &prio_in) != MSG_SIZE) \
shutdown(3, "Test receive failure", __LINE__); \
clock_gettime(clock, &end); \
nsec = ((middle.tv_sec - start.tv_sec) * 1000000000) + \
(middle.tv_nsec - start.tv_nsec); \
send_total.tv_nsec += nsec; \
if (send_total.tv_nsec >= 1000000000) { \
send_total.tv_sec++; \
send_total.tv_nsec -= 1000000000; \
} \
nsec = ((end.tv_sec - middle.tv_sec) * 1000000000) + \
(end.tv_nsec - middle.tv_nsec); \
recv_total.tv_nsec += nsec; \
if (recv_total.tv_nsec >= 1000000000) { \
recv_total.tv_sec++; \
recv_total.tv_nsec -= 1000000000; \
} \
} while (0)
struct test {
char *desc;
void (*func)(int *);
};
void const_prio(int *prio)
{
return;
}
void inc_prio(int *prio)
{
if (++*prio == mq_prio_max)
*prio = 0;
}
void dec_prio(int *prio)
{
if (--*prio < 0)
*prio = mq_prio_max - 1;
}
void random_prio(int *prio)
{
*prio = random() % mq_prio_max;
}
struct test test2[] = {
{"\n\tTest #2a: Time send/recv message, queue full, constant prio\n",
const_prio},
{"\n\tTest #2b: Time send/recv message, queue full, increasing prio\n",
inc_prio},
{"\n\tTest #2c: Time send/recv message, queue full, decreasing prio\n",
dec_prio},
{"\n\tTest #2d: Time send/recv message, queue full, random prio\n",
random_prio},
{NULL, NULL}
};
/**
* Tests to perform (all done with MSG_SIZE messages):
*
* 1) Time to add/remove message with 0 messages on queue
* 1a) with constant prio
* 2) Time to add/remove message when queue close to capacity:
* 2a) with constant prio
* 2b) with increasing prio
* 2c) with decreasing prio
* 2d) with random prio
* 3) Test limits of priorities honored (double check _SC_MQ_PRIO_MAX)
*/
void *perf_test_thread(void *arg)
{
char buff[MSG_SIZE];
int prio_out, prio_in;
int i;
clockid_t clock;
pthread_t *t;
struct timespec res, start, middle, end, send_total, recv_total;
unsigned long long nsec;
struct test *cur_test;
t = &cpu_threads[0];
printf("\n\tStarted mqueue performance test thread on CPU %d\n",
cpus_to_pin[0]);
mq_prio_max = sysconf(_SC_MQ_PRIO_MAX);
if (mq_prio_max == -1)
shutdown(2, "sysconf(_SC_MQ_PRIO_MAX)", __LINE__);
if (pthread_getcpuclockid(cpu_threads[0], &clock) != 0)
shutdown(2, "pthread_getcpuclockid", __LINE__);
if (clock_getres(clock, &res))
shutdown(2, "clock_getres()", __LINE__);
printf("\t\tMax priorities:\t\t\t%d\n", mq_prio_max);
printf("\t\tClock resolution:\t\t%d nsec%s\n", res.tv_nsec,
res.tv_nsec > 1 ? "s" : "");
printf("\n\tTest #1: Time send/recv message, queue empty\n");
printf("\t\t(%d iterations)\n", TEST1_LOOPS);
prio_out = 0;
send_total.tv_sec = 0;
send_total.tv_nsec = 0;
recv_total.tv_sec = 0;
recv_total.tv_nsec = 0;
for (i = 0; i < TEST1_LOOPS; i++)
do_send_recv();
printf("\t\tSend msg:\t\t\t%d.%ds total time\n",
send_total.tv_sec, send_total.tv_nsec);
nsec = ((unsigned long long)send_total.tv_sec * 1000000000 +
send_total.tv_nsec) / TEST1_LOOPS;
printf("\t\t\t\t\t\t%d nsec/msg\n", nsec);
printf("\t\tRecv msg:\t\t\t%d.%ds total time\n",
recv_total.tv_sec, recv_total.tv_nsec);
nsec = ((unsigned long long)recv_total.tv_sec * 1000000000 +
recv_total.tv_nsec) / TEST1_LOOPS;
printf("\t\t\t\t\t\t%d nsec/msg\n", nsec);
for (cur_test = test2; cur_test->desc != NULL; cur_test++) {
printf(cur_test->desc);
printf("\t\t(%d iterations)\n", TEST2_LOOPS);
prio_out = 0;
send_total.tv_sec = 0;
send_total.tv_nsec = 0;
recv_total.tv_sec = 0;
recv_total.tv_nsec = 0;
printf("\t\tFilling queue...");
fflush(stdout);
clock_gettime(clock, &start);
for (i = 0; i < result.mq_maxmsg - 1; i++) {
do_untimed_send();
cur_test->func(&prio_out);
}
clock_gettime(clock, &end);
nsec = ((unsigned long long)(end.tv_sec - start.tv_sec) *
1000000000) + (end.tv_nsec - start.tv_nsec);
printf("done.\t\t%lld.%llds\n", nsec / 1000000000,
nsec % 1000000000);
printf("\t\tTesting...");
fflush(stdout);
for (i = 0; i < TEST2_LOOPS; i++) {
do_send_recv();
cur_test->func(&prio_out);
}
printf("done.\n");
printf("\t\tSend msg:\t\t\t%d.%ds total time\n",
send_total.tv_sec, send_total.tv_nsec);
nsec = ((unsigned long long)send_total.tv_sec * 1000000000 +
send_total.tv_nsec) / TEST2_LOOPS;
printf("\t\t\t\t\t\t%d nsec/msg\n", nsec);
printf("\t\tRecv msg:\t\t\t%d.%ds total time\n",
recv_total.tv_sec, recv_total.tv_nsec);
nsec = ((unsigned long long)recv_total.tv_sec * 1000000000 +
recv_total.tv_nsec) / TEST2_LOOPS;
printf("\t\t\t\t\t\t%d nsec/msg\n", nsec);
printf("\t\tDraining queue...");
fflush(stdout);
clock_gettime(clock, &start);
drain_queue();
clock_gettime(clock, &end);
nsec = ((unsigned long long)(end.tv_sec - start.tv_sec) *
1000000000) + (end.tv_nsec - start.tv_nsec);
printf("done.\t\t%lld.%llds\n", nsec / 1000000000,
nsec % 1000000000);
}
return 0;
}
void increase_limits(void)
{
cur_limits.rlim_cur = RLIM_INFINITY;
cur_limits.rlim_max = RLIM_INFINITY;
setr(RLIMIT_MSGQUEUE, &cur_limits);
while (try_set(max_msgs, cur_max_msgs += 10))
;
cur_max_msgs = get(max_msgs);
while (try_set(max_msgsize, cur_max_msgsize += 1024))
;
cur_max_msgsize = get(max_msgsize);
if (setpriority(PRIO_PROCESS, 0, -20) != 0)
shutdown(2, "setpriority()", __LINE__);
cur_nice = -20;
}
int main(int argc, char *argv[])
{
struct mq_attr attr;
char *option, *next_option;
int i, cpu;
struct sigaction sa;
poptContext popt_context;
char rc;
void *retval;
main_thread = pthread_self();
num_cpus_to_pin = 0;
if (sysconf(_SC_NPROCESSORS_ONLN) == -1) {
perror("sysconf(_SC_NPROCESSORS_ONLN)");
exit(1);
}
cpus_online = min(MAX_CPUS, sysconf(_SC_NPROCESSORS_ONLN));
cpu_set = CPU_ALLOC(cpus_online);
if (cpu_set == NULL) {
perror("CPU_ALLOC()");
exit(1);
}
cpu_set_size = CPU_ALLOC_SIZE(cpus_online);
CPU_ZERO_S(cpu_set_size, cpu_set);
popt_context = poptGetContext(NULL, argc, (const char **)argv,
options, 0);
while ((rc = poptGetNextOpt(popt_context)) > 0) {
switch (rc) {
case 'c':
continuous_mode = 1;
option = cpu_option_string;
do {
next_option = strchr(option, ',');
if (next_option)
*next_option = '\0';
cpu = atoi(option);
if (cpu >= cpus_online)
fprintf(stderr, "CPU %d exceeds "
"cpus online, ignoring.\n",
cpu);
else
cpus_to_pin[num_cpus_to_pin++] = cpu;
if (next_option)
option = ++next_option;
} while (next_option && num_cpus_to_pin < MAX_CPUS);
/* Double check that they didn't give us the same CPU
* more than once */
for (cpu = 0; cpu < num_cpus_to_pin; cpu++) {
if (CPU_ISSET_S(cpus_to_pin[cpu], cpu_set_size,
cpu_set)) {
fprintf(stderr, "Any given CPU may "
"only be given once.\n");
exit(1);
} else
CPU_SET_S(cpus_to_pin[cpu],
cpu_set_size, cpu_set);
}
break;
case 'p':
/*
* Although we can create a msg queue with a
* non-absolute path name, unlink will fail. So,
* if the name doesn't start with a /, add one
* when we save it.
*/
option = queue_path;
if (*option != '/') {
queue_path = malloc(strlen(option) + 2);
if (!queue_path) {
perror("malloc()");
exit(1);
}
queue_path[0] = '/';
queue_path[1] = 0;
strcat(queue_path, option);
free(option);
}
break;
}
}
if (continuous_mode && num_cpus_to_pin == 0) {
fprintf(stderr, "Must pass at least one CPU to continuous "
"mode.\n");
poptPrintUsage(popt_context, stderr, 0);
exit(1);
} else if (!continuous_mode) {
num_cpus_to_pin = 1;
cpus_to_pin[0] = cpus_online - 1;
}
if (getuid() != 0) {
fprintf(stderr, "Not running as root, but almost all tests "
"require root in order to modify\nsystem settings. "
"Exiting.\n");
exit(1);
}
max_msgs = fopen(MAX_MSGS, "r+");
max_msgsize = fopen(MAX_MSGSIZE, "r+");
if (!max_msgs)
shutdown(2, "Failed to open msg_max", __LINE__);
if (!max_msgsize)
shutdown(2, "Failed to open msgsize_max", __LINE__);
/* Load up the current system values for everything we can */
getr(RLIMIT_MSGQUEUE, &saved_limits);
cur_limits = saved_limits;
saved_max_msgs = cur_max_msgs = get(max_msgs);
saved_max_msgsize = cur_max_msgsize = get(max_msgsize);
errno = 0;
cur_nice = getpriority(PRIO_PROCESS, 0);
if (errno)
shutdown(2, "getpriority()", __LINE__);
/* Tell the user our initial state */
printf("\nInitial system state:\n");
printf("\tUsing queue path:\t\t\t%s\n", queue_path);
printf("\tRLIMIT_MSGQUEUE(soft):\t\t\t%d\n", saved_limits.rlim_cur);
printf("\tRLIMIT_MSGQUEUE(hard):\t\t\t%d\n", saved_limits.rlim_max);
printf("\tMaximum Message Size:\t\t\t%d\n", saved_max_msgsize);
printf("\tMaximum Queue Size:\t\t\t%d\n", saved_max_msgs);
printf("\tNice value:\t\t\t\t%d\n", cur_nice);
printf("\n");
increase_limits();
printf("Adjusted system state for testing:\n");
if (cur_limits.rlim_cur == RLIM_INFINITY) {
printf("\tRLIMIT_MSGQUEUE(soft):\t\t\t(unlimited)\n");
printf("\tRLIMIT_MSGQUEUE(hard):\t\t\t(unlimited)\n");
} else {
printf("\tRLIMIT_MSGQUEUE(soft):\t\t\t%d\n",
cur_limits.rlim_cur);
printf("\tRLIMIT_MSGQUEUE(hard):\t\t\t%d\n",
cur_limits.rlim_max);
}
printf("\tMaximum Message Size:\t\t\t%d\n", cur_max_msgsize);
printf("\tMaximum Queue Size:\t\t\t%d\n", cur_max_msgs);
printf("\tNice value:\t\t\t\t%d\n", cur_nice);
printf("\tContinuous mode:\t\t\t(%s)\n", continuous_mode ?
(continuous_mode_fake ? "fake mode" : "enabled") :
"disabled");
printf("\tCPUs to pin:\t\t\t\t%d", cpus_to_pin[0]);
for (cpu = 1; cpu < num_cpus_to_pin; cpu++)
printf(",%d", cpus_to_pin[cpu]);
printf("\n");
sa.sa_sigaction = sig_action_SIGUSR1;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGHUP);
sigaddset(&sa.sa_mask, SIGINT);
sigaddset(&sa.sa_mask, SIGQUIT);
sigaddset(&sa.sa_mask, SIGTERM);
sa.sa_flags = SA_SIGINFO;
if (sigaction(SIGUSR1, &sa, NULL) == -1)
shutdown(1, "sigaction(SIGUSR1)", __LINE__);
sa.sa_sigaction = sig_action;
if (sigaction(SIGHUP, &sa, NULL) == -1)
shutdown(1, "sigaction(SIGHUP)", __LINE__);
if (sigaction(SIGINT, &sa, NULL) == -1)
shutdown(1, "sigaction(SIGINT)", __LINE__);
if (sigaction(SIGQUIT, &sa, NULL) == -1)
shutdown(1, "sigaction(SIGQUIT)", __LINE__);
if (sigaction(SIGTERM, &sa, NULL) == -1)
shutdown(1, "sigaction(SIGTERM)", __LINE__);
if (!continuous_mode_fake) {
attr.mq_flags = O_NONBLOCK;
attr.mq_maxmsg = cur_max_msgs;
attr.mq_msgsize = MSG_SIZE;
open_queue(&attr);
}
for (i = 0; i < num_cpus_to_pin; i++) {
pthread_attr_t thread_attr;
void *thread_func;
if (continuous_mode_fake)
thread_func = &fake_cont_thread;
else if (continuous_mode)
thread_func = &cont_thread;
else
thread_func = &perf_test_thread;
CPU_ZERO_S(cpu_set_size, cpu_set);
CPU_SET_S(cpus_to_pin[i], cpu_set_size, cpu_set);
pthread_attr_init(&thread_attr);
pthread_attr_setaffinity_np(&thread_attr, cpu_set_size,
cpu_set);
if (pthread_create(&cpu_threads[i], &thread_attr, thread_func,
NULL))
shutdown(1, "pthread_create()", __LINE__);
pthread_attr_destroy(&thread_attr);
}
if (!continuous_mode) {
pthread_join(cpu_threads[0], &retval);
shutdown((long)retval, "perf_test_thread()", __LINE__);
} else {
while (1)
sleep(1);
}
shutdown(0, "", 0);
}

View File

@@ -0,0 +1,3 @@
socket
psock_fanout
psock_tpacket

View File

@@ -0,0 +1,19 @@
# Makefile for net selftests
CC = $(CROSS_COMPILE)gcc
CFLAGS = -Wall -O2 -g
CFLAGS += -I../../../../usr/include/
NET_PROGS = socket psock_fanout psock_tpacket
all: $(NET_PROGS)
%: %.c
$(CC) $(CFLAGS) -o $@ $^
run_tests: all
@/bin/sh ./run_netsocktests || echo "sockettests: [FAIL]"
@/bin/sh ./run_afpackettests || echo "afpackettests: [FAIL]"
clean:
$(RM) $(NET_PROGS)

View File

@@ -0,0 +1,312 @@
/*
* Copyright 2013 Google Inc.
* Author: Willem de Bruijn (willemb@google.com)
*
* A basic test of packet socket fanout behavior.
*
* Control:
* - create fanout fails as expected with illegal flag combinations
* - join fanout fails as expected with diverging types or flags
*
* Datapath:
* Open a pair of packet sockets and a pair of INET sockets, send a known
* number of packets across the two INET sockets and count the number of
* packets enqueued onto the two packet sockets.
*
* The test currently runs for
* - PACKET_FANOUT_HASH
* - PACKET_FANOUT_HASH with PACKET_FANOUT_FLAG_ROLLOVER
* - PACKET_FANOUT_LB
* - PACKET_FANOUT_CPU
* - PACKET_FANOUT_ROLLOVER
*
* Todo:
* - functionality: PACKET_FANOUT_FLAG_DEFRAG
*
* License (GPLv2):
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
*/
#define _GNU_SOURCE /* for sched_setaffinity */
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/filter.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <poll.h>
#include <sched.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "psock_lib.h"
#define RING_NUM_FRAMES 20
/* Open a socket in a given fanout mode.
* @return -1 if mode is bad, a valid socket otherwise */
static int sock_fanout_open(uint16_t typeflags, int num_packets)
{
int fd, val;
fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP));
if (fd < 0) {
perror("socket packet");
exit(1);
}
/* fanout group ID is always 0: tests whether old groups are deleted */
val = ((int) typeflags) << 16;
if (setsockopt(fd, SOL_PACKET, PACKET_FANOUT, &val, sizeof(val))) {
if (close(fd)) {
perror("close packet");
exit(1);
}
return -1;
}
pair_udp_setfilter(fd);
return fd;
}
static char *sock_fanout_open_ring(int fd)
{
struct tpacket_req req = {
.tp_block_size = getpagesize(),
.tp_frame_size = getpagesize(),
.tp_block_nr = RING_NUM_FRAMES,
.tp_frame_nr = RING_NUM_FRAMES,
};
char *ring;
int val = TPACKET_V2;
if (setsockopt(fd, SOL_PACKET, PACKET_VERSION, (void *) &val,
sizeof(val))) {
perror("packetsock ring setsockopt version");
exit(1);
}
if (setsockopt(fd, SOL_PACKET, PACKET_RX_RING, (void *) &req,
sizeof(req))) {
perror("packetsock ring setsockopt");
exit(1);
}
ring = mmap(0, req.tp_block_size * req.tp_block_nr,
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (!ring) {
fprintf(stderr, "packetsock ring mmap\n");
exit(1);
}
return ring;
}
static int sock_fanout_read_ring(int fd, void *ring)
{
struct tpacket2_hdr *header = ring;
int count = 0;
while (header->tp_status & TP_STATUS_USER && count < RING_NUM_FRAMES) {
count++;
header = ring + (count * getpagesize());
}
return count;
}
static int sock_fanout_read(int fds[], char *rings[], const int expect[])
{
int ret[2];
ret[0] = sock_fanout_read_ring(fds[0], rings[0]);
ret[1] = sock_fanout_read_ring(fds[1], rings[1]);
fprintf(stderr, "info: count=%d,%d, expect=%d,%d\n",
ret[0], ret[1], expect[0], expect[1]);
if ((!(ret[0] == expect[0] && ret[1] == expect[1])) &&
(!(ret[0] == expect[1] && ret[1] == expect[0]))) {
fprintf(stderr, "ERROR: incorrect queue lengths\n");
return 1;
}
return 0;
}
/* Test illegal mode + flag combination */
static void test_control_single(void)
{
fprintf(stderr, "test: control single socket\n");
if (sock_fanout_open(PACKET_FANOUT_ROLLOVER |
PACKET_FANOUT_FLAG_ROLLOVER, 0) != -1) {
fprintf(stderr, "ERROR: opened socket with dual rollover\n");
exit(1);
}
}
/* Test illegal group with different modes or flags */
static void test_control_group(void)
{
int fds[2];
fprintf(stderr, "test: control multiple sockets\n");
fds[0] = sock_fanout_open(PACKET_FANOUT_HASH, 20);
if (fds[0] == -1) {
fprintf(stderr, "ERROR: failed to open HASH socket\n");
exit(1);
}
if (sock_fanout_open(PACKET_FANOUT_HASH |
PACKET_FANOUT_FLAG_DEFRAG, 10) != -1) {
fprintf(stderr, "ERROR: joined group with wrong flag defrag\n");
exit(1);
}
if (sock_fanout_open(PACKET_FANOUT_HASH |
PACKET_FANOUT_FLAG_ROLLOVER, 10) != -1) {
fprintf(stderr, "ERROR: joined group with wrong flag ro\n");
exit(1);
}
if (sock_fanout_open(PACKET_FANOUT_CPU, 10) != -1) {
fprintf(stderr, "ERROR: joined group with wrong mode\n");
exit(1);
}
fds[1] = sock_fanout_open(PACKET_FANOUT_HASH, 20);
if (fds[1] == -1) {
fprintf(stderr, "ERROR: failed to join group\n");
exit(1);
}
if (close(fds[1]) || close(fds[0])) {
fprintf(stderr, "ERROR: closing sockets\n");
exit(1);
}
}
static int test_datapath(uint16_t typeflags, int port_off,
const int expect1[], const int expect2[])
{
const int expect0[] = { 0, 0 };
char *rings[2];
int fds[2], fds_udp[2][2], ret;
fprintf(stderr, "test: datapath 0x%hx\n", typeflags);
fds[0] = sock_fanout_open(typeflags, 20);
fds[1] = sock_fanout_open(typeflags, 20);
if (fds[0] == -1 || fds[1] == -1) {
fprintf(stderr, "ERROR: failed open\n");
exit(1);
}
rings[0] = sock_fanout_open_ring(fds[0]);
rings[1] = sock_fanout_open_ring(fds[1]);
pair_udp_open(fds_udp[0], PORT_BASE);
pair_udp_open(fds_udp[1], PORT_BASE + port_off);
sock_fanout_read(fds, rings, expect0);
/* Send data, but not enough to overflow a queue */
pair_udp_send(fds_udp[0], 15);
pair_udp_send(fds_udp[1], 5);
ret = sock_fanout_read(fds, rings, expect1);
/* Send more data, overflow the queue */
pair_udp_send(fds_udp[0], 15);
/* TODO: ensure consistent order between expect1 and expect2 */
ret |= sock_fanout_read(fds, rings, expect2);
if (munmap(rings[1], RING_NUM_FRAMES * getpagesize()) ||
munmap(rings[0], RING_NUM_FRAMES * getpagesize())) {
fprintf(stderr, "close rings\n");
exit(1);
}
if (close(fds_udp[1][1]) || close(fds_udp[1][0]) ||
close(fds_udp[0][1]) || close(fds_udp[0][0]) ||
close(fds[1]) || close(fds[0])) {
fprintf(stderr, "close datapath\n");
exit(1);
}
return ret;
}
static int set_cpuaffinity(int cpuid)
{
cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(cpuid, &mask);
if (sched_setaffinity(0, sizeof(mask), &mask)) {
if (errno != EINVAL) {
fprintf(stderr, "setaffinity %d\n", cpuid);
exit(1);
}
return 1;
}
return 0;
}
int main(int argc, char **argv)
{
const int expect_hash[2][2] = { { 15, 5 }, { 20, 5 } };
const int expect_hash_rb[2][2] = { { 15, 5 }, { 20, 15 } };
const int expect_lb[2][2] = { { 10, 10 }, { 18, 17 } };
const int expect_rb[2][2] = { { 20, 0 }, { 20, 15 } };
const int expect_cpu0[2][2] = { { 20, 0 }, { 20, 0 } };
const int expect_cpu1[2][2] = { { 0, 20 }, { 0, 20 } };
int port_off = 2, tries = 5, ret;
test_control_single();
test_control_group();
/* find a set of ports that do not collide onto the same socket */
ret = test_datapath(PACKET_FANOUT_HASH, port_off,
expect_hash[0], expect_hash[1]);
while (ret && tries--) {
fprintf(stderr, "info: trying alternate ports (%d)\n", tries);
ret = test_datapath(PACKET_FANOUT_HASH, ++port_off,
expect_hash[0], expect_hash[1]);
}
ret |= test_datapath(PACKET_FANOUT_HASH | PACKET_FANOUT_FLAG_ROLLOVER,
port_off, expect_hash_rb[0], expect_hash_rb[1]);
ret |= test_datapath(PACKET_FANOUT_LB,
port_off, expect_lb[0], expect_lb[1]);
ret |= test_datapath(PACKET_FANOUT_ROLLOVER,
port_off, expect_rb[0], expect_rb[1]);
set_cpuaffinity(0);
ret |= test_datapath(PACKET_FANOUT_CPU, port_off,
expect_cpu0[0], expect_cpu0[1]);
if (!set_cpuaffinity(1))
/* TODO: test that choice alternates with previous */
ret |= test_datapath(PACKET_FANOUT_CPU, port_off,
expect_cpu1[0], expect_cpu1[1]);
if (ret)
return 1;
printf("OK. All tests passed\n");
return 0;
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright 2013 Google Inc.
* Author: Willem de Bruijn <willemb@google.com>
* Daniel Borkmann <dborkman@redhat.com>
*
* License (GPLv2):
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef PSOCK_LIB_H
#define PSOCK_LIB_H
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#define DATA_LEN 100
#define DATA_CHAR 'a'
#define PORT_BASE 8000
#ifndef __maybe_unused
# define __maybe_unused __attribute__ ((__unused__))
#endif
static __maybe_unused void pair_udp_setfilter(int fd)
{
struct sock_filter bpf_filter[] = {
{ 0x80, 0, 0, 0x00000000 }, /* LD pktlen */
{ 0x35, 0, 5, DATA_LEN }, /* JGE DATA_LEN [f goto nomatch]*/
{ 0x30, 0, 0, 0x00000050 }, /* LD ip[80] */
{ 0x15, 0, 3, DATA_CHAR }, /* JEQ DATA_CHAR [f goto nomatch]*/
{ 0x30, 0, 0, 0x00000051 }, /* LD ip[81] */
{ 0x15, 0, 1, DATA_CHAR }, /* JEQ DATA_CHAR [f goto nomatch]*/
{ 0x06, 0, 0, 0x00000060 }, /* RET match */
{ 0x06, 0, 0, 0x00000000 }, /* RET no match */
};
struct sock_fprog bpf_prog;
bpf_prog.filter = bpf_filter;
bpf_prog.len = sizeof(bpf_filter) / sizeof(struct sock_filter);
if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &bpf_prog,
sizeof(bpf_prog))) {
perror("setsockopt SO_ATTACH_FILTER");
exit(1);
}
}
static __maybe_unused void pair_udp_open(int fds[], uint16_t port)
{
struct sockaddr_in saddr, daddr;
fds[0] = socket(PF_INET, SOCK_DGRAM, 0);
fds[1] = socket(PF_INET, SOCK_DGRAM, 0);
if (fds[0] == -1 || fds[1] == -1) {
fprintf(stderr, "ERROR: socket dgram\n");
exit(1);
}
memset(&saddr, 0, sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(port);
saddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
memset(&daddr, 0, sizeof(daddr));
daddr.sin_family = AF_INET;
daddr.sin_port = htons(port + 1);
daddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
/* must bind both to get consistent hash result */
if (bind(fds[1], (void *) &daddr, sizeof(daddr))) {
perror("bind");
exit(1);
}
if (bind(fds[0], (void *) &saddr, sizeof(saddr))) {
perror("bind");
exit(1);
}
if (connect(fds[0], (void *) &daddr, sizeof(daddr))) {
perror("connect");
exit(1);
}
}
static __maybe_unused void pair_udp_send(int fds[], int num)
{
char buf[DATA_LEN], rbuf[DATA_LEN];
memset(buf, DATA_CHAR, sizeof(buf));
while (num--) {
/* Should really handle EINTR and EAGAIN */
if (write(fds[0], buf, sizeof(buf)) != sizeof(buf)) {
fprintf(stderr, "ERROR: send failed left=%d\n", num);
exit(1);
}
if (read(fds[1], rbuf, sizeof(rbuf)) != sizeof(rbuf)) {
fprintf(stderr, "ERROR: recv failed left=%d\n", num);
exit(1);
}
if (memcmp(buf, rbuf, sizeof(buf))) {
fprintf(stderr, "ERROR: data failed left=%d\n", num);
exit(1);
}
}
}
static __maybe_unused void pair_udp_close(int fds[])
{
close(fds[0]);
close(fds[1]);
}
#endif /* PSOCK_LIB_H */

View File

@@ -0,0 +1,824 @@
/*
* Copyright 2013 Red Hat, Inc.
* Author: Daniel Borkmann <dborkman@redhat.com>
*
* A basic test of packet socket's TPACKET_V1/TPACKET_V2/TPACKET_V3 behavior.
*
* Control:
* Test the setup of the TPACKET socket with different patterns that are
* known to fail (TODO) resp. succeed (OK).
*
* Datapath:
* Open a pair of packet sockets and send resp. receive an a priori known
* packet pattern accross the sockets and check if it was received resp.
* sent correctly. Fanout in combination with RX_RING is currently not
* tested here.
*
* The test currently runs for
* - TPACKET_V1: RX_RING, TX_RING
* - TPACKET_V2: RX_RING, TX_RING
* - TPACKET_V3: RX_RING
*
* License (GPLv2):
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <linux/if_packet.h>
#include <linux/filter.h>
#include <ctype.h>
#include <fcntl.h>
#include <unistd.h>
#include <bits/wordsize.h>
#include <net/ethernet.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include <net/if.h>
#include <inttypes.h>
#include <poll.h>
#include "psock_lib.h"
#ifndef bug_on
# define bug_on(cond) assert(!(cond))
#endif
#ifndef __aligned_tpacket
# define __aligned_tpacket __attribute__((aligned(TPACKET_ALIGNMENT)))
#endif
#ifndef __align_tpacket
# define __align_tpacket(x) __attribute__((aligned(TPACKET_ALIGN(x))))
#endif
#define BLOCK_STATUS(x) ((x)->h1.block_status)
#define BLOCK_NUM_PKTS(x) ((x)->h1.num_pkts)
#define BLOCK_O2FP(x) ((x)->h1.offset_to_first_pkt)
#define BLOCK_LEN(x) ((x)->h1.blk_len)
#define BLOCK_SNUM(x) ((x)->h1.seq_num)
#define BLOCK_O2PRIV(x) ((x)->offset_to_priv)
#define BLOCK_PRIV(x) ((void *) ((uint8_t *) (x) + BLOCK_O2PRIV(x)))
#define BLOCK_HDR_LEN (ALIGN_8(sizeof(struct block_desc)))
#define ALIGN_8(x) (((x) + 8 - 1) & ~(8 - 1))
#define BLOCK_PLUS_PRIV(sz_pri) (BLOCK_HDR_LEN + ALIGN_8((sz_pri)))
#define NUM_PACKETS 100
struct ring {
struct iovec *rd;
uint8_t *mm_space;
size_t mm_len, rd_len;
struct sockaddr_ll ll;
void (*walk)(int sock, struct ring *ring);
int type, rd_num, flen, version;
union {
struct tpacket_req req;
struct tpacket_req3 req3;
};
};
struct block_desc {
uint32_t version;
uint32_t offset_to_priv;
struct tpacket_hdr_v1 h1;
};
union frame_map {
struct {
struct tpacket_hdr tp_h __aligned_tpacket;
struct sockaddr_ll s_ll __align_tpacket(sizeof(struct tpacket_hdr));
} *v1;
struct {
struct tpacket2_hdr tp_h __aligned_tpacket;
struct sockaddr_ll s_ll __align_tpacket(sizeof(struct tpacket2_hdr));
} *v2;
void *raw;
};
static unsigned int total_packets, total_bytes;
static int pfsocket(int ver)
{
int ret, sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sock == -1) {
perror("socket");
exit(1);
}
ret = setsockopt(sock, SOL_PACKET, PACKET_VERSION, &ver, sizeof(ver));
if (ret == -1) {
perror("setsockopt");
exit(1);
}
return sock;
}
static void status_bar_update(void)
{
if (total_packets % 10 == 0) {
fprintf(stderr, ".");
fflush(stderr);
}
}
static void test_payload(void *pay, size_t len)
{
struct ethhdr *eth = pay;
if (len < sizeof(struct ethhdr)) {
fprintf(stderr, "test_payload: packet too "
"small: %zu bytes!\n", len);
exit(1);
}
if (eth->h_proto != htons(ETH_P_IP)) {
fprintf(stderr, "test_payload: wrong ethernet "
"type: 0x%x!\n", ntohs(eth->h_proto));
exit(1);
}
}
static void create_payload(void *pay, size_t *len)
{
int i;
struct ethhdr *eth = pay;
struct iphdr *ip = pay + sizeof(*eth);
/* Lets create some broken crap, that still passes
* our BPF filter.
*/
*len = DATA_LEN + 42;
memset(pay, 0xff, ETH_ALEN * 2);
eth->h_proto = htons(ETH_P_IP);
for (i = 0; i < sizeof(*ip); ++i)
((uint8_t *) pay)[i + sizeof(*eth)] = (uint8_t) rand();
ip->ihl = 5;
ip->version = 4;
ip->protocol = 0x11;
ip->frag_off = 0;
ip->ttl = 64;
ip->tot_len = htons((uint16_t) *len - sizeof(*eth));
ip->saddr = htonl(INADDR_LOOPBACK);
ip->daddr = htonl(INADDR_LOOPBACK);
memset(pay + sizeof(*eth) + sizeof(*ip),
DATA_CHAR, DATA_LEN);
}
static inline int __v1_rx_kernel_ready(struct tpacket_hdr *hdr)
{
return ((hdr->tp_status & TP_STATUS_USER) == TP_STATUS_USER);
}
static inline void __v1_rx_user_ready(struct tpacket_hdr *hdr)
{
hdr->tp_status = TP_STATUS_KERNEL;
__sync_synchronize();
}
static inline int __v2_rx_kernel_ready(struct tpacket2_hdr *hdr)
{
return ((hdr->tp_status & TP_STATUS_USER) == TP_STATUS_USER);
}
static inline void __v2_rx_user_ready(struct tpacket2_hdr *hdr)
{
hdr->tp_status = TP_STATUS_KERNEL;
__sync_synchronize();
}
static inline int __v1_v2_rx_kernel_ready(void *base, int version)
{
switch (version) {
case TPACKET_V1:
return __v1_rx_kernel_ready(base);
case TPACKET_V2:
return __v2_rx_kernel_ready(base);
default:
bug_on(1);
return 0;
}
}
static inline void __v1_v2_rx_user_ready(void *base, int version)
{
switch (version) {
case TPACKET_V1:
__v1_rx_user_ready(base);
break;
case TPACKET_V2:
__v2_rx_user_ready(base);
break;
}
}
static void walk_v1_v2_rx(int sock, struct ring *ring)
{
struct pollfd pfd;
int udp_sock[2];
union frame_map ppd;
unsigned int frame_num = 0;
bug_on(ring->type != PACKET_RX_RING);
pair_udp_open(udp_sock, PORT_BASE);
pair_udp_setfilter(sock);
memset(&pfd, 0, sizeof(pfd));
pfd.fd = sock;
pfd.events = POLLIN | POLLERR;
pfd.revents = 0;
pair_udp_send(udp_sock, NUM_PACKETS);
while (total_packets < NUM_PACKETS * 2) {
while (__v1_v2_rx_kernel_ready(ring->rd[frame_num].iov_base,
ring->version)) {
ppd.raw = ring->rd[frame_num].iov_base;
switch (ring->version) {
case TPACKET_V1:
test_payload((uint8_t *) ppd.raw + ppd.v1->tp_h.tp_mac,
ppd.v1->tp_h.tp_snaplen);
total_bytes += ppd.v1->tp_h.tp_snaplen;
break;
case TPACKET_V2:
test_payload((uint8_t *) ppd.raw + ppd.v2->tp_h.tp_mac,
ppd.v2->tp_h.tp_snaplen);
total_bytes += ppd.v2->tp_h.tp_snaplen;
break;
}
status_bar_update();
total_packets++;
__v1_v2_rx_user_ready(ppd.raw, ring->version);
frame_num = (frame_num + 1) % ring->rd_num;
}
poll(&pfd, 1, 1);
}
pair_udp_close(udp_sock);
if (total_packets != 2 * NUM_PACKETS) {
fprintf(stderr, "walk_v%d_rx: received %u out of %u pkts\n",
ring->version, total_packets, NUM_PACKETS);
exit(1);
}
fprintf(stderr, " %u pkts (%u bytes)", NUM_PACKETS, total_bytes >> 1);
}
static inline int __v1_tx_kernel_ready(struct tpacket_hdr *hdr)
{
return !(hdr->tp_status & (TP_STATUS_SEND_REQUEST | TP_STATUS_SENDING));
}
static inline void __v1_tx_user_ready(struct tpacket_hdr *hdr)
{
hdr->tp_status = TP_STATUS_SEND_REQUEST;
__sync_synchronize();
}
static inline int __v2_tx_kernel_ready(struct tpacket2_hdr *hdr)
{
return !(hdr->tp_status & (TP_STATUS_SEND_REQUEST | TP_STATUS_SENDING));
}
static inline void __v2_tx_user_ready(struct tpacket2_hdr *hdr)
{
hdr->tp_status = TP_STATUS_SEND_REQUEST;
__sync_synchronize();
}
static inline int __v1_v2_tx_kernel_ready(void *base, int version)
{
switch (version) {
case TPACKET_V1:
return __v1_tx_kernel_ready(base);
case TPACKET_V2:
return __v2_tx_kernel_ready(base);
default:
bug_on(1);
return 0;
}
}
static inline void __v1_v2_tx_user_ready(void *base, int version)
{
switch (version) {
case TPACKET_V1:
__v1_tx_user_ready(base);
break;
case TPACKET_V2:
__v2_tx_user_ready(base);
break;
}
}
static void __v1_v2_set_packet_loss_discard(int sock)
{
int ret, discard = 1;
ret = setsockopt(sock, SOL_PACKET, PACKET_LOSS, (void *) &discard,
sizeof(discard));
if (ret == -1) {
perror("setsockopt");
exit(1);
}
}
static void walk_v1_v2_tx(int sock, struct ring *ring)
{
struct pollfd pfd;
int rcv_sock, ret;
size_t packet_len;
union frame_map ppd;
char packet[1024];
unsigned int frame_num = 0, got = 0;
struct sockaddr_ll ll = {
.sll_family = PF_PACKET,
.sll_halen = ETH_ALEN,
};
bug_on(ring->type != PACKET_TX_RING);
bug_on(ring->rd_num < NUM_PACKETS);
rcv_sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (rcv_sock == -1) {
perror("socket");
exit(1);
}
pair_udp_setfilter(rcv_sock);
ll.sll_ifindex = if_nametoindex("lo");
ret = bind(rcv_sock, (struct sockaddr *) &ll, sizeof(ll));
if (ret == -1) {
perror("bind");
exit(1);
}
memset(&pfd, 0, sizeof(pfd));
pfd.fd = sock;
pfd.events = POLLOUT | POLLERR;
pfd.revents = 0;
total_packets = NUM_PACKETS;
create_payload(packet, &packet_len);
while (total_packets > 0) {
while (__v1_v2_tx_kernel_ready(ring->rd[frame_num].iov_base,
ring->version) &&
total_packets > 0) {
ppd.raw = ring->rd[frame_num].iov_base;
switch (ring->version) {
case TPACKET_V1:
ppd.v1->tp_h.tp_snaplen = packet_len;
ppd.v1->tp_h.tp_len = packet_len;
memcpy((uint8_t *) ppd.raw + TPACKET_HDRLEN -
sizeof(struct sockaddr_ll), packet,
packet_len);
total_bytes += ppd.v1->tp_h.tp_snaplen;
break;
case TPACKET_V2:
ppd.v2->tp_h.tp_snaplen = packet_len;
ppd.v2->tp_h.tp_len = packet_len;
memcpy((uint8_t *) ppd.raw + TPACKET2_HDRLEN -
sizeof(struct sockaddr_ll), packet,
packet_len);
total_bytes += ppd.v2->tp_h.tp_snaplen;
break;
}
status_bar_update();
total_packets--;
__v1_v2_tx_user_ready(ppd.raw, ring->version);
frame_num = (frame_num + 1) % ring->rd_num;
}
poll(&pfd, 1, 1);
}
bug_on(total_packets != 0);
ret = sendto(sock, NULL, 0, 0, NULL, 0);
if (ret == -1) {
perror("sendto");
exit(1);
}
while ((ret = recvfrom(rcv_sock, packet, sizeof(packet),
0, NULL, NULL)) > 0 &&
total_packets < NUM_PACKETS) {
got += ret;
test_payload(packet, ret);
status_bar_update();
total_packets++;
}
close(rcv_sock);
if (total_packets != NUM_PACKETS) {
fprintf(stderr, "walk_v%d_rx: received %u out of %u pkts\n",
ring->version, total_packets, NUM_PACKETS);
exit(1);
}
fprintf(stderr, " %u pkts (%u bytes)", NUM_PACKETS, got);
}
static void walk_v1_v2(int sock, struct ring *ring)
{
if (ring->type == PACKET_RX_RING)
walk_v1_v2_rx(sock, ring);
else
walk_v1_v2_tx(sock, ring);
}
static uint64_t __v3_prev_block_seq_num = 0;
void __v3_test_block_seq_num(struct block_desc *pbd)
{
if (__v3_prev_block_seq_num + 1 != BLOCK_SNUM(pbd)) {
fprintf(stderr, "\nprev_block_seq_num:%"PRIu64", expected "
"seq:%"PRIu64" != actual seq:%"PRIu64"\n",
__v3_prev_block_seq_num, __v3_prev_block_seq_num + 1,
(uint64_t) BLOCK_SNUM(pbd));
exit(1);
}
__v3_prev_block_seq_num = BLOCK_SNUM(pbd);
}
static void __v3_test_block_len(struct block_desc *pbd, uint32_t bytes, int block_num)
{
if (BLOCK_NUM_PKTS(pbd)) {
if (bytes != BLOCK_LEN(pbd)) {
fprintf(stderr, "\nblock:%u with %upackets, expected "
"len:%u != actual len:%u\n", block_num,
BLOCK_NUM_PKTS(pbd), bytes, BLOCK_LEN(pbd));
exit(1);
}
} else {
if (BLOCK_LEN(pbd) != BLOCK_PLUS_PRIV(13)) {
fprintf(stderr, "\nblock:%u, expected len:%lu != "
"actual len:%u\n", block_num, BLOCK_HDR_LEN,
BLOCK_LEN(pbd));
exit(1);
}
}
}
static void __v3_test_block_header(struct block_desc *pbd, const int block_num)
{
uint32_t block_status = BLOCK_STATUS(pbd);
if ((block_status & TP_STATUS_USER) == 0) {
fprintf(stderr, "\nblock %u: not in TP_STATUS_USER\n", block_num);
exit(1);
}
__v3_test_block_seq_num(pbd);
}
static void __v3_walk_block(struct block_desc *pbd, const int block_num)
{
int num_pkts = BLOCK_NUM_PKTS(pbd), i;
unsigned long bytes = 0;
unsigned long bytes_with_padding = BLOCK_PLUS_PRIV(13);
struct tpacket3_hdr *ppd;
__v3_test_block_header(pbd, block_num);
ppd = (struct tpacket3_hdr *) ((uint8_t *) pbd + BLOCK_O2FP(pbd));
for (i = 0; i < num_pkts; ++i) {
bytes += ppd->tp_snaplen;
if (ppd->tp_next_offset)
bytes_with_padding += ppd->tp_next_offset;
else
bytes_with_padding += ALIGN_8(ppd->tp_snaplen + ppd->tp_mac);
test_payload((uint8_t *) ppd + ppd->tp_mac, ppd->tp_snaplen);
status_bar_update();
total_packets++;
ppd = (struct tpacket3_hdr *) ((uint8_t *) ppd + ppd->tp_next_offset);
__sync_synchronize();
}
__v3_test_block_len(pbd, bytes_with_padding, block_num);
total_bytes += bytes;
}
void __v3_flush_block(struct block_desc *pbd)
{
BLOCK_STATUS(pbd) = TP_STATUS_KERNEL;
__sync_synchronize();
}
static void walk_v3_rx(int sock, struct ring *ring)
{
unsigned int block_num = 0;
struct pollfd pfd;
struct block_desc *pbd;
int udp_sock[2];
bug_on(ring->type != PACKET_RX_RING);
pair_udp_open(udp_sock, PORT_BASE);
pair_udp_setfilter(sock);
memset(&pfd, 0, sizeof(pfd));
pfd.fd = sock;
pfd.events = POLLIN | POLLERR;
pfd.revents = 0;
pair_udp_send(udp_sock, NUM_PACKETS);
while (total_packets < NUM_PACKETS * 2) {
pbd = (struct block_desc *) ring->rd[block_num].iov_base;
while ((BLOCK_STATUS(pbd) & TP_STATUS_USER) == 0)
poll(&pfd, 1, 1);
__v3_walk_block(pbd, block_num);
__v3_flush_block(pbd);
block_num = (block_num + 1) % ring->rd_num;
}
pair_udp_close(udp_sock);
if (total_packets != 2 * NUM_PACKETS) {
fprintf(stderr, "walk_v3_rx: received %u out of %u pkts\n",
total_packets, NUM_PACKETS);
exit(1);
}
fprintf(stderr, " %u pkts (%u bytes)", NUM_PACKETS, total_bytes >> 1);
}
static void walk_v3(int sock, struct ring *ring)
{
if (ring->type == PACKET_RX_RING)
walk_v3_rx(sock, ring);
else
bug_on(1);
}
static void __v1_v2_fill(struct ring *ring, unsigned int blocks)
{
ring->req.tp_block_size = getpagesize() << 2;
ring->req.tp_frame_size = TPACKET_ALIGNMENT << 7;
ring->req.tp_block_nr = blocks;
ring->req.tp_frame_nr = ring->req.tp_block_size /
ring->req.tp_frame_size *
ring->req.tp_block_nr;
ring->mm_len = ring->req.tp_block_size * ring->req.tp_block_nr;
ring->walk = walk_v1_v2;
ring->rd_num = ring->req.tp_frame_nr;
ring->flen = ring->req.tp_frame_size;
}
static void __v3_fill(struct ring *ring, unsigned int blocks)
{
ring->req3.tp_retire_blk_tov = 64;
ring->req3.tp_sizeof_priv = 13;
ring->req3.tp_feature_req_word |= TP_FT_REQ_FILL_RXHASH;
ring->req3.tp_block_size = getpagesize() << 2;
ring->req3.tp_frame_size = TPACKET_ALIGNMENT << 7;
ring->req3.tp_block_nr = blocks;
ring->req3.tp_frame_nr = ring->req3.tp_block_size /
ring->req3.tp_frame_size *
ring->req3.tp_block_nr;
ring->mm_len = ring->req3.tp_block_size * ring->req3.tp_block_nr;
ring->walk = walk_v3;
ring->rd_num = ring->req3.tp_block_nr;
ring->flen = ring->req3.tp_block_size;
}
static void setup_ring(int sock, struct ring *ring, int version, int type)
{
int ret = 0;
unsigned int blocks = 256;
ring->type = type;
ring->version = version;
switch (version) {
case TPACKET_V1:
case TPACKET_V2:
if (type == PACKET_TX_RING)
__v1_v2_set_packet_loss_discard(sock);
__v1_v2_fill(ring, blocks);
ret = setsockopt(sock, SOL_PACKET, type, &ring->req,
sizeof(ring->req));
break;
case TPACKET_V3:
__v3_fill(ring, blocks);
ret = setsockopt(sock, SOL_PACKET, type, &ring->req3,
sizeof(ring->req3));
break;
}
if (ret == -1) {
perror("setsockopt");
exit(1);
}
ring->rd_len = ring->rd_num * sizeof(*ring->rd);
ring->rd = malloc(ring->rd_len);
if (ring->rd == NULL) {
perror("malloc");
exit(1);
}
total_packets = 0;
total_bytes = 0;
}
static void mmap_ring(int sock, struct ring *ring)
{
int i;
ring->mm_space = mmap(0, ring->mm_len, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_LOCKED | MAP_POPULATE, sock, 0);
if (ring->mm_space == MAP_FAILED) {
perror("mmap");
exit(1);
}
memset(ring->rd, 0, ring->rd_len);
for (i = 0; i < ring->rd_num; ++i) {
ring->rd[i].iov_base = ring->mm_space + (i * ring->flen);
ring->rd[i].iov_len = ring->flen;
}
}
static void bind_ring(int sock, struct ring *ring)
{
int ret;
ring->ll.sll_family = PF_PACKET;
ring->ll.sll_protocol = htons(ETH_P_ALL);
ring->ll.sll_ifindex = if_nametoindex("lo");
ring->ll.sll_hatype = 0;
ring->ll.sll_pkttype = 0;
ring->ll.sll_halen = 0;
ret = bind(sock, (struct sockaddr *) &ring->ll, sizeof(ring->ll));
if (ret == -1) {
perror("bind");
exit(1);
}
}
static void walk_ring(int sock, struct ring *ring)
{
ring->walk(sock, ring);
}
static void unmap_ring(int sock, struct ring *ring)
{
munmap(ring->mm_space, ring->mm_len);
free(ring->rd);
}
static int test_kernel_bit_width(void)
{
char in[512], *ptr;
int num = 0, fd;
ssize_t ret;
fd = open("/proc/kallsyms", O_RDONLY);
if (fd == -1) {
perror("open");
exit(1);
}
ret = read(fd, in, sizeof(in));
if (ret <= 0) {
perror("read");
exit(1);
}
close(fd);
ptr = in;
while(!isspace(*ptr)) {
num++;
ptr++;
}
return num * 4;
}
static int test_user_bit_width(void)
{
return __WORDSIZE;
}
static const char *tpacket_str[] = {
[TPACKET_V1] = "TPACKET_V1",
[TPACKET_V2] = "TPACKET_V2",
[TPACKET_V3] = "TPACKET_V3",
};
static const char *type_str[] = {
[PACKET_RX_RING] = "PACKET_RX_RING",
[PACKET_TX_RING] = "PACKET_TX_RING",
};
static int test_tpacket(int version, int type)
{
int sock;
struct ring ring;
fprintf(stderr, "test: %s with %s ", tpacket_str[version],
type_str[type]);
fflush(stderr);
if (version == TPACKET_V1 &&
test_kernel_bit_width() != test_user_bit_width()) {
fprintf(stderr, "test: skip %s %s since user and kernel "
"space have different bit width\n",
tpacket_str[version], type_str[type]);
return 0;
}
sock = pfsocket(version);
memset(&ring, 0, sizeof(ring));
setup_ring(sock, &ring, version, type);
mmap_ring(sock, &ring);
bind_ring(sock, &ring);
walk_ring(sock, &ring);
unmap_ring(sock, &ring);
close(sock);
fprintf(stderr, "\n");
return 0;
}
int main(void)
{
int ret = 0;
ret |= test_tpacket(TPACKET_V1, PACKET_RX_RING);
ret |= test_tpacket(TPACKET_V1, PACKET_TX_RING);
ret |= test_tpacket(TPACKET_V2, PACKET_RX_RING);
ret |= test_tpacket(TPACKET_V2, PACKET_TX_RING);
ret |= test_tpacket(TPACKET_V3, PACKET_RX_RING);
if (ret)
return 1;
printf("OK. All tests passed\n");
return 0;
}

View File

@@ -0,0 +1,26 @@
#!/bin/sh
if [ $(id -u) != 0 ]; then
echo $msg must be run as root >&2
exit 0
fi
echo "--------------------"
echo "running psock_fanout test"
echo "--------------------"
./psock_fanout
if [ $? -ne 0 ]; then
echo "[FAIL]"
else
echo "[PASS]"
fi
echo "--------------------"
echo "running psock_tpacket test"
echo "--------------------"
./psock_tpacket
if [ $? -ne 0 ]; then
echo "[FAIL]"
else
echo "[PASS]"
fi

View File

@@ -0,0 +1,12 @@
#!/bin/bash
echo "--------------------"
echo "running socket test"
echo "--------------------"
./socket
if [ $? -ne 0 ]; then
echo "[FAIL]"
else
echo "[PASS]"
fi

View File

@@ -0,0 +1,92 @@
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
struct socket_testcase {
int domain;
int type;
int protocol;
/* 0 = valid file descriptor
* -foo = error foo
*/
int expect;
/* If non-zero, accept EAFNOSUPPORT to handle the case
* of the protocol not being configured into the kernel.
*/
int nosupport_ok;
};
static struct socket_testcase tests[] = {
{ AF_MAX, 0, 0, -EAFNOSUPPORT, 0 },
{ AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 1 },
{ AF_INET, SOCK_DGRAM, IPPROTO_TCP, -EPROTONOSUPPORT, 1 },
{ AF_INET, SOCK_DGRAM, IPPROTO_UDP, 0, 1 },
{ AF_INET, SOCK_STREAM, IPPROTO_UDP, -EPROTONOSUPPORT, 1 },
};
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
#define ERR_STRING_SZ 64
static int run_tests(void)
{
char err_string1[ERR_STRING_SZ];
char err_string2[ERR_STRING_SZ];
int i, err;
err = 0;
for (i = 0; i < ARRAY_SIZE(tests); i++) {
struct socket_testcase *s = &tests[i];
int fd;
fd = socket(s->domain, s->type, s->protocol);
if (fd < 0) {
if (s->nosupport_ok &&
errno == EAFNOSUPPORT)
continue;
if (s->expect < 0 &&
errno == -s->expect)
continue;
strerror_r(-s->expect, err_string1, ERR_STRING_SZ);
strerror_r(errno, err_string2, ERR_STRING_SZ);
fprintf(stderr, "socket(%d, %d, %d) expected "
"err (%s) got (%s)\n",
s->domain, s->type, s->protocol,
err_string1, err_string2);
err = -1;
break;
} else {
close(fd);
if (s->expect < 0) {
strerror_r(errno, err_string1, ERR_STRING_SZ);
fprintf(stderr, "socket(%d, %d, %d) expected "
"success got err (%s)\n",
s->domain, s->type, s->protocol,
err_string1);
err = -1;
break;
}
}
}
return err;
}
int main(void)
{
int err = run_tests();
return err;
}

View File

@@ -0,0 +1,10 @@
CFLAGS += -iquote../../../../include/uapi -Wall
peeksiginfo: peeksiginfo.c
all: peeksiginfo
clean:
rm -f peeksiginfo
run_tests: all
@./peeksiginfo || echo "peeksiginfo selftests: [FAIL]"

View File

@@ -0,0 +1,214 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <linux/types.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <sys/user.h>
#include <sys/mman.h>
#include "linux/ptrace.h"
static int sys_rt_sigqueueinfo(pid_t tgid, int sig, siginfo_t *uinfo)
{
return syscall(SYS_rt_sigqueueinfo, tgid, sig, uinfo);
}
static int sys_rt_tgsigqueueinfo(pid_t tgid, pid_t tid,
int sig, siginfo_t *uinfo)
{
return syscall(SYS_rt_tgsigqueueinfo, tgid, tid, sig, uinfo);
}
static int sys_ptrace(int request, pid_t pid, void *addr, void *data)
{
return syscall(SYS_ptrace, request, pid, addr, data);
}
#define SIGNR 10
#define TEST_SICODE_PRIV -1
#define TEST_SICODE_SHARE -2
#define err(fmt, ...) \
fprintf(stderr, \
"Error (%s:%d): " fmt, \
__FILE__, __LINE__, ##__VA_ARGS__)
static int check_error_paths(pid_t child)
{
struct ptrace_peeksiginfo_args arg;
int ret, exit_code = -1;
void *addr_rw, *addr_ro;
/*
* Allocate two contiguous pages. The first one is for read-write,
* another is for read-only.
*/
addr_rw = mmap(NULL, 2 * PAGE_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (addr_rw == MAP_FAILED) {
err("mmap() failed: %m\n");
return 1;
}
addr_ro = mmap(addr_rw + PAGE_SIZE, PAGE_SIZE, PROT_READ,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
if (addr_ro == MAP_FAILED) {
err("mmap() failed: %m\n");
goto out;
}
arg.nr = SIGNR;
arg.off = 0;
/* Unsupported flags */
arg.flags = ~0;
ret = sys_ptrace(PTRACE_PEEKSIGINFO, child, &arg, addr_rw);
if (ret != -1 || errno != EINVAL) {
err("sys_ptrace() returns %d (expected -1),"
" errno %d (expected %d): %m\n",
ret, errno, EINVAL);
goto out;
}
arg.flags = 0;
/* A part of the buffer is read-only */
ret = sys_ptrace(PTRACE_PEEKSIGINFO, child, &arg,
addr_ro - sizeof(siginfo_t) * 2);
if (ret != 2) {
err("sys_ptrace() returns %d (expected 2): %m\n", ret);
goto out;
}
/* Read-only buffer */
ret = sys_ptrace(PTRACE_PEEKSIGINFO, child, &arg, addr_ro);
if (ret != -1 && errno != EFAULT) {
err("sys_ptrace() returns %d (expected -1),"
" errno %d (expected %d): %m\n",
ret, errno, EFAULT);
goto out;
}
exit_code = 0;
out:
munmap(addr_rw, 2 * PAGE_SIZE);
return exit_code;
}
int check_direct_path(pid_t child, int shared, int nr)
{
struct ptrace_peeksiginfo_args arg = {.flags = 0, .nr = nr, .off = 0};
int i, j, ret, exit_code = -1;
siginfo_t siginfo[SIGNR];
int si_code;
if (shared == 1) {
arg.flags = PTRACE_PEEKSIGINFO_SHARED;
si_code = TEST_SICODE_SHARE;
} else {
arg.flags = 0;
si_code = TEST_SICODE_PRIV;
}
for (i = 0; i < SIGNR; ) {
arg.off = i;
ret = sys_ptrace(PTRACE_PEEKSIGINFO, child, &arg, siginfo);
if (ret == -1) {
err("ptrace() failed: %m\n");
goto out;
}
if (ret == 0)
break;
for (j = 0; j < ret; j++, i++) {
if (siginfo[j].si_code == si_code &&
siginfo[j].si_int == i)
continue;
err("%d: Wrong siginfo i=%d si_code=%d si_int=%d\n",
shared, i, siginfo[j].si_code, siginfo[j].si_int);
goto out;
}
}
if (i != SIGNR) {
err("Only %d signals were read\n", i);
goto out;
}
exit_code = 0;
out:
return exit_code;
}
int main(int argc, char *argv[])
{
siginfo_t siginfo[SIGNR];
int i, exit_code = 1;
sigset_t blockmask;
pid_t child;
sigemptyset(&blockmask);
sigaddset(&blockmask, SIGRTMIN);
sigprocmask(SIG_BLOCK, &blockmask, NULL);
child = fork();
if (child == -1) {
err("fork() failed: %m");
return 1;
} else if (child == 0) {
pid_t ppid = getppid();
while (1) {
if (ppid != getppid())
break;
sleep(1);
}
return 1;
}
/* Send signals in process-wide and per-thread queues */
for (i = 0; i < SIGNR; i++) {
siginfo->si_code = TEST_SICODE_SHARE;
siginfo->si_int = i;
sys_rt_sigqueueinfo(child, SIGRTMIN, siginfo);
siginfo->si_code = TEST_SICODE_PRIV;
siginfo->si_int = i;
sys_rt_tgsigqueueinfo(child, child, SIGRTMIN, siginfo);
}
if (sys_ptrace(PTRACE_ATTACH, child, NULL, NULL) == -1)
return 1;
waitpid(child, NULL, 0);
/* Dump signals one by one*/
if (check_direct_path(child, 0, 1))
goto out;
/* Dump all signals for one call */
if (check_direct_path(child, 0, SIGNR))
goto out;
/*
* Dump signal from the process-wide queue.
* The number of signals is not multible to the buffer size
*/
if (check_direct_path(child, 1, 3))
goto out;
if (check_error_paths(child))
goto out;
printf("PASS\n");
exit_code = 0;
out:
if (sys_ptrace(PTRACE_KILL, child, NULL, NULL) == -1)
return 1;
waitpid(child, NULL, 0);
return exit_code;
}

View File

@@ -0,0 +1,14 @@
# Makefile for vm selftests
CC = $(CROSS_COMPILE)gcc
CFLAGS = -Wall
all: hugepage-mmap hugepage-shm map_hugetlb thuge-gen
%: %.c
$(CC) $(CFLAGS) -o $@ $^
run_tests: all
@/bin/sh ./run_vmtests || echo "vmtests: [FAIL]"
clean:
$(RM) hugepage-mmap hugepage-shm map_hugetlb

View File

@@ -0,0 +1,92 @@
/*
* hugepage-mmap:
*
* Example of using huge page memory in a user application using the mmap
* system call. Before running this application, make sure that the
* administrator has mounted the hugetlbfs filesystem (on some directory
* like /mnt) using the command mount -t hugetlbfs nodev /mnt. In this
* example, the app is requesting memory of size 256MB that is backed by
* huge pages.
*
* For the ia64 architecture, the Linux kernel reserves Region number 4 for
* huge pages. That means that if one requires a fixed address, a huge page
* aligned address starting with 0x800000... will be required. If a fixed
* address is not required, the kernel will select an address in the proper
* range.
* Other architectures, such as ppc64, i386 or x86_64 are not so constrained.
*/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#define FILE_NAME "huge/hugepagefile"
#define LENGTH (256UL*1024*1024)
#define PROTECTION (PROT_READ | PROT_WRITE)
/* Only ia64 requires this */
#ifdef __ia64__
#define ADDR (void *)(0x8000000000000000UL)
#define FLAGS (MAP_SHARED | MAP_FIXED)
#else
#define ADDR (void *)(0x0UL)
#define FLAGS (MAP_SHARED)
#endif
static void check_bytes(char *addr)
{
printf("First hex is %x\n", *((unsigned int *)addr));
}
static void write_bytes(char *addr)
{
unsigned long i;
for (i = 0; i < LENGTH; i++)
*(addr + i) = (char)i;
}
static int read_bytes(char *addr)
{
unsigned long i;
check_bytes(addr);
for (i = 0; i < LENGTH; i++)
if (*(addr + i) != (char)i) {
printf("Mismatch at %lu\n", i);
return 1;
}
return 0;
}
int main(void)
{
void *addr;
int fd, ret;
fd = open(FILE_NAME, O_CREAT | O_RDWR, 0755);
if (fd < 0) {
perror("Open failed");
exit(1);
}
addr = mmap(ADDR, LENGTH, PROTECTION, FLAGS, fd, 0);
if (addr == MAP_FAILED) {
perror("mmap");
unlink(FILE_NAME);
exit(1);
}
printf("Returned address is %p\n", addr);
check_bytes(addr);
write_bytes(addr);
ret = read_bytes(addr);
munmap(addr, LENGTH);
close(fd);
unlink(FILE_NAME);
return ret;
}

View File

@@ -0,0 +1,100 @@
/*
* hugepage-shm:
*
* Example of using huge page memory in a user application using Sys V shared
* memory system calls. In this example the app is requesting 256MB of
* memory that is backed by huge pages. The application uses the flag
* SHM_HUGETLB in the shmget system call to inform the kernel that it is
* requesting huge pages.
*
* For the ia64 architecture, the Linux kernel reserves Region number 4 for
* huge pages. That means that if one requires a fixed address, a huge page
* aligned address starting with 0x800000... will be required. If a fixed
* address is not required, the kernel will select an address in the proper
* range.
* Other architectures, such as ppc64, i386 or x86_64 are not so constrained.
*
* Note: The default shared memory limit is quite low on many kernels,
* you may need to increase it via:
*
* echo 268435456 > /proc/sys/kernel/shmmax
*
* This will increase the maximum size per shared memory segment to 256MB.
* The other limit that you will hit eventually is shmall which is the
* total amount of shared memory in pages. To set it to 16GB on a system
* with a 4kB pagesize do:
*
* echo 4194304 > /proc/sys/kernel/shmall
*/
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/mman.h>
#ifndef SHM_HUGETLB
#define SHM_HUGETLB 04000
#endif
#define LENGTH (256UL*1024*1024)
#define dprintf(x) printf(x)
/* Only ia64 requires this */
#ifdef __ia64__
#define ADDR (void *)(0x8000000000000000UL)
#define SHMAT_FLAGS (SHM_RND)
#else
#define ADDR (void *)(0x0UL)
#define SHMAT_FLAGS (0)
#endif
int main(void)
{
int shmid;
unsigned long i;
char *shmaddr;
shmid = shmget(2, LENGTH, SHM_HUGETLB | IPC_CREAT | SHM_R | SHM_W);
if (shmid < 0) {
perror("shmget");
exit(1);
}
printf("shmid: 0x%x\n", shmid);
shmaddr = shmat(shmid, ADDR, SHMAT_FLAGS);
if (shmaddr == (char *)-1) {
perror("Shared memory attach failure");
shmctl(shmid, IPC_RMID, NULL);
exit(2);
}
printf("shmaddr: %p\n", shmaddr);
dprintf("Starting the writes:\n");
for (i = 0; i < LENGTH; i++) {
shmaddr[i] = (char)(i);
if (!(i % (1024 * 1024)))
dprintf(".");
}
dprintf("\n");
dprintf("Starting the Check...");
for (i = 0; i < LENGTH; i++)
if (shmaddr[i] != (char)i) {
printf("\nIndex %lu mismatched\n", i);
exit(3);
}
dprintf("Done.\n");
if (shmdt((const void *)shmaddr) != 0) {
perror("Detach failure");
shmctl(shmid, IPC_RMID, NULL);
exit(4);
}
shmctl(shmid, IPC_RMID, NULL);
return 0;
}

View File

@@ -0,0 +1,79 @@
/*
* Example of using hugepage memory in a user application using the mmap
* system call with MAP_HUGETLB flag. Before running this program make
* sure the administrator has allocated enough default sized huge pages
* to cover the 256 MB allocation.
*
* For ia64 architecture, Linux kernel reserves Region number 4 for hugepages.
* That means the addresses starting with 0x800000... will need to be
* specified. Specifying a fixed address is not required on ppc64, i386
* or x86_64.
*/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#define LENGTH (256UL*1024*1024)
#define PROTECTION (PROT_READ | PROT_WRITE)
#ifndef MAP_HUGETLB
#define MAP_HUGETLB 0x40000 /* arch specific */
#endif
/* Only ia64 requires this */
#ifdef __ia64__
#define ADDR (void *)(0x8000000000000000UL)
#define FLAGS (MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB | MAP_FIXED)
#else
#define ADDR (void *)(0x0UL)
#define FLAGS (MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB)
#endif
static void check_bytes(char *addr)
{
printf("First hex is %x\n", *((unsigned int *)addr));
}
static void write_bytes(char *addr)
{
unsigned long i;
for (i = 0; i < LENGTH; i++)
*(addr + i) = (char)i;
}
static int read_bytes(char *addr)
{
unsigned long i;
check_bytes(addr);
for (i = 0; i < LENGTH; i++)
if (*(addr + i) != (char)i) {
printf("Mismatch at %lu\n", i);
return 1;
}
return 0;
}
int main(void)
{
void *addr;
int ret;
addr = mmap(ADDR, LENGTH, PROTECTION, FLAGS, 0, 0);
if (addr == MAP_FAILED) {
perror("mmap");
exit(1);
}
printf("Returned address is %p\n", addr);
check_bytes(addr);
write_bytes(addr);
ret = read_bytes(addr);
munmap(addr, LENGTH);
return ret;
}

View File

@@ -0,0 +1,77 @@
#!/bin/bash
#please run as root
#we need 256M, below is the size in kB
needmem=262144
mnt=./huge
#get pagesize and freepages from /proc/meminfo
while read name size unit; do
if [ "$name" = "HugePages_Free:" ]; then
freepgs=$size
fi
if [ "$name" = "Hugepagesize:" ]; then
pgsize=$size
fi
done < /proc/meminfo
#set proper nr_hugepages
if [ -n "$freepgs" ] && [ -n "$pgsize" ]; then
nr_hugepgs=`cat /proc/sys/vm/nr_hugepages`
needpgs=`expr $needmem / $pgsize`
if [ $freepgs -lt $needpgs ]; then
lackpgs=$(( $needpgs - $freepgs ))
echo $(( $lackpgs + $nr_hugepgs )) > /proc/sys/vm/nr_hugepages
if [ $? -ne 0 ]; then
echo "Please run this test as root"
exit 1
fi
fi
else
echo "no hugetlbfs support in kernel?"
exit 1
fi
mkdir $mnt
mount -t hugetlbfs none $mnt
echo "--------------------"
echo "running hugepage-mmap"
echo "--------------------"
./hugepage-mmap
if [ $? -ne 0 ]; then
echo "[FAIL]"
else
echo "[PASS]"
fi
shmmax=`cat /proc/sys/kernel/shmmax`
shmall=`cat /proc/sys/kernel/shmall`
echo 268435456 > /proc/sys/kernel/shmmax
echo 4194304 > /proc/sys/kernel/shmall
echo "--------------------"
echo "running hugepage-shm"
echo "--------------------"
./hugepage-shm
if [ $? -ne 0 ]; then
echo "[FAIL]"
else
echo "[PASS]"
fi
echo $shmmax > /proc/sys/kernel/shmmax
echo $shmall > /proc/sys/kernel/shmall
echo "--------------------"
echo "running map_hugetlb"
echo "--------------------"
./map_hugetlb
if [ $? -ne 0 ]; then
echo "[FAIL]"
else
echo "[PASS]"
fi
#cleanup
umount $mnt
rm -rf $mnt
echo $nr_hugepgs > /proc/sys/vm/nr_hugepages

View File

@@ -0,0 +1,254 @@
/* Test selecting other page sizes for mmap/shmget.
Before running this huge pages for each huge page size must have been
reserved.
For large pages beyond MAX_ORDER (like 1GB on x86) boot options must be used.
Also shmmax must be increased.
And you need to run as root to work around some weird permissions in shm.
And nothing using huge pages should run in parallel.
When the program aborts you may need to clean up the shm segments with
ipcrm -m by hand, like this
sudo ipcs | awk '$1 == "0x00000000" {print $2}' | xargs -n1 sudo ipcrm -m
(warning this will remove all if someone else uses them) */
#define _GNU_SOURCE 1
#include <sys/mman.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <glob.h>
#include <assert.h>
#include <unistd.h>
#include <stdarg.h>
#include <string.h>
#define err(x) perror(x), exit(1)
#define MAP_HUGE_2MB (21 << MAP_HUGE_SHIFT)
#define MAP_HUGE_1GB (30 << MAP_HUGE_SHIFT)
#define MAP_HUGE_SHIFT 26
#define MAP_HUGE_MASK 0x3f
#define MAP_HUGETLB 0x40000
#define SHM_HUGETLB 04000 /* segment will use huge TLB pages */
#define SHM_HUGE_SHIFT 26
#define SHM_HUGE_MASK 0x3f
#define SHM_HUGE_2MB (21 << SHM_HUGE_SHIFT)
#define SHM_HUGE_1GB (30 << SHM_HUGE_SHIFT)
#define NUM_PAGESIZES 5
#define NUM_PAGES 4
#define Dprintf(fmt...) // printf(fmt)
unsigned long page_sizes[NUM_PAGESIZES];
int num_page_sizes;
int ilog2(unsigned long v)
{
int l = 0;
while ((1UL << l) < v)
l++;
return l;
}
void find_pagesizes(void)
{
glob_t g;
int i;
glob("/sys/kernel/mm/hugepages/hugepages-*kB", 0, NULL, &g);
assert(g.gl_pathc <= NUM_PAGESIZES);
for (i = 0; i < g.gl_pathc; i++) {
sscanf(g.gl_pathv[i], "/sys/kernel/mm/hugepages/hugepages-%lukB",
&page_sizes[i]);
page_sizes[i] <<= 10;
printf("Found %luMB\n", page_sizes[i] >> 20);
}
num_page_sizes = g.gl_pathc;
globfree(&g);
}
unsigned long default_huge_page_size(void)
{
unsigned long hps = 0;
char *line = NULL;
size_t linelen = 0;
FILE *f = fopen("/proc/meminfo", "r");
if (!f)
return 0;
while (getline(&line, &linelen, f) > 0) {
if (sscanf(line, "Hugepagesize: %lu kB", &hps) == 1) {
hps <<= 10;
break;
}
}
free(line);
return hps;
}
void show(unsigned long ps)
{
char buf[100];
if (ps == getpagesize())
return;
printf("%luMB: ", ps >> 20);
fflush(stdout);
snprintf(buf, sizeof buf,
"cat /sys/kernel/mm/hugepages/hugepages-%lukB/free_hugepages",
ps >> 10);
system(buf);
}
unsigned long read_sysfs(int warn, char *fmt, ...)
{
char *line = NULL;
size_t linelen = 0;
char buf[100];
FILE *f;
va_list ap;
unsigned long val = 0;
va_start(ap, fmt);
vsnprintf(buf, sizeof buf, fmt, ap);
va_end(ap);
f = fopen(buf, "r");
if (!f) {
if (warn)
printf("missing %s\n", buf);
return 0;
}
if (getline(&line, &linelen, f) > 0) {
sscanf(line, "%lu", &val);
}
fclose(f);
free(line);
return val;
}
unsigned long read_free(unsigned long ps)
{
return read_sysfs(ps != getpagesize(),
"/sys/kernel/mm/hugepages/hugepages-%lukB/free_hugepages",
ps >> 10);
}
void test_mmap(unsigned long size, unsigned flags)
{
char *map;
unsigned long before, after;
int err;
before = read_free(size);
map = mmap(NULL, size*NUM_PAGES, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS|MAP_HUGETLB|flags, 0, 0);
if (map == (char *)-1) err("mmap");
memset(map, 0xff, size*NUM_PAGES);
after = read_free(size);
Dprintf("before %lu after %lu diff %ld size %lu\n",
before, after, before - after, size);
assert(size == getpagesize() || (before - after) == NUM_PAGES);
show(size);
err = munmap(map, size);
assert(!err);
}
void test_shmget(unsigned long size, unsigned flags)
{
int id;
unsigned long before, after;
int err;
before = read_free(size);
id = shmget(IPC_PRIVATE, size * NUM_PAGES, IPC_CREAT|0600|flags);
if (id < 0) err("shmget");
struct shm_info i;
if (shmctl(id, SHM_INFO, (void *)&i) < 0) err("shmctl");
Dprintf("alloc %lu res %lu\n", i.shm_tot, i.shm_rss);
Dprintf("id %d\n", id);
char *map = shmat(id, NULL, 0600);
if (map == (char*)-1) err("shmat");
shmctl(id, IPC_RMID, NULL);
memset(map, 0xff, size*NUM_PAGES);
after = read_free(size);
Dprintf("before %lu after %lu diff %ld size %lu\n",
before, after, before - after, size);
assert(size == getpagesize() || (before - after) == NUM_PAGES);
show(size);
err = shmdt(map);
assert(!err);
}
void sanity_checks(void)
{
int i;
unsigned long largest = getpagesize();
for (i = 0; i < num_page_sizes; i++) {
if (page_sizes[i] > largest)
largest = page_sizes[i];
if (read_free(page_sizes[i]) < NUM_PAGES) {
printf("Not enough huge pages for page size %lu MB, need %u\n",
page_sizes[i] >> 20,
NUM_PAGES);
exit(0);
}
}
if (read_sysfs(0, "/proc/sys/kernel/shmmax") < NUM_PAGES * largest) {
printf("Please do echo %lu > /proc/sys/kernel/shmmax", largest * NUM_PAGES);
exit(0);
}
#if defined(__x86_64__)
if (largest != 1U<<30) {
printf("No GB pages available on x86-64\n"
"Please boot with hugepagesz=1G hugepages=%d\n", NUM_PAGES);
exit(0);
}
#endif
}
int main(void)
{
int i;
unsigned default_hps = default_huge_page_size();
find_pagesizes();
sanity_checks();
for (i = 0; i < num_page_sizes; i++) {
unsigned long ps = page_sizes[i];
int arg = ilog2(ps) << MAP_HUGE_SHIFT;
printf("Testing %luMB mmap with shift %x\n", ps >> 20, arg);
test_mmap(ps, MAP_HUGETLB | arg);
}
printf("Testing default huge mmap\n");
test_mmap(default_hps, SHM_HUGETLB);
puts("Testing non-huge shmget");
test_shmget(getpagesize(), 0);
for (i = 0; i < num_page_sizes; i++) {
unsigned long ps = page_sizes[i];
int arg = ilog2(ps) << SHM_HUGE_SHIFT;
printf("Testing %luMB shmget with shift %x\n", ps >> 20, arg);
test_shmget(ps, SHM_HUGETLB | arg);
}
puts("default huge shmget");
test_shmget(default_hps, SHM_HUGETLB);
return 0;
}