I currently started learning the Linux Device driver programming in Linux. where I found this small piece of code printing hello world using printk() function.
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void)
{
printk(KERN_ALERT "Hello World!!!\n");
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT "Goodbye Hello World!!!\n");
}
module_init(hello_init);
module_exit(hello_exit);
After compiling code using make command and load driver using insmod command. I'm not getting the "Hello world" printed on screen instead its printing only on the log file /var/log/kern.log. But I want printk to print on my ubuntu terminal. I'm using ubuntu(14.04). Is it possible?
It isn't possible to redirect kernel logs and massages to gnome-terminal and there you have to use dmesg.
But in a virtual terminal(open one with ctrl+F1-F6) you can redirect them to standard output.
First determine tty number by entering tty command in virtual terminal.The output may be /dev/tty(1-6).
Compile and run this code with the argument you specified.
/*
* setconsole.c -- choose a console to receive kernel messages
*
* Copyright (C) 1998,2000,2001 Alessandro Rubini
*
* This program 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/ioctl.h>
int main(int argc, char **argv)
{
char bytes[2] = {11,0}; /* 11 is the TIOCLINUX cmd number */
if (argc==2) bytes[1] = atoi(argv[1]); /* the chosen console */
else {
fprintf(stderr, "%s: need a single arg\n",argv[0]); exit(1);
}
if (ioctl(STDIN_FILENO, TIOCLINUX, bytes)<0) { /* use stdin */
fprintf(stderr,"%s: ioctl(stdin, TIOCLINUX): %s\n",
argv[0], strerror(errno));
exit(1);
}
exit(0);
}
For example if your output for tty command was /dev/tty1then type this two command:
gcc setconsole.c -o setconsole
sudo ./setconsole 1
This will set your tty to receive kernel messages.
Then compile and run this code.
/*
* setlevel.c -- choose a console_loglevel for the kernel
*
* Copyright (C) 1998,2000,2001 Alessandro Rubini
*
* This program 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/klog.h>
int main(int argc, char **argv)
{
int level;
if (argc==2) {
level = atoi(argv[1]); /* the chosen console */
} else {
fprintf(stderr, "%s: need a single arg\n",argv[0]); exit(1);
}
if (klogctl(8,NULL,level) < 0) {
fprintf(stderr,"%s: syslog(setlevel): %s\n",
argv[0],strerror(errno));
exit(1);
}
exit(0);
}
There are 8 level of kernel messages as you specify in your code KERN_ALERT is one of them.To make console receive all of them you should run above code with 8 as arguement.
gcc setlevel.c -o setlevel
sudo ./setlevel 8
Now you can insert your module to kernel and see kernel logs in console.
By the way these codes are from ldd3 examples.
printk prints to the kernel log. There is no "screen" as far as the kernel is concerned. If you want to see the output of printk in real time, you can open a terminal and type the following dmesg -w. Note that the -w flag is only supported by recent versions of dmesg (which is provided by the util-linux package).
Related
I'm trying to cross-compile a simple C program to aarch64 (arm64) from a 64bit Ubuntu Linux. Can someone please help me why i'm getting this error.
It says 'cpuid.h' is not found. I've tried compiling it on the 64bit linux, it works fine. But when using aarch64-linux-gnu-gcc it is giving errors.
I'm getting the following error.
aarch64-linux-gnu-gcc -O1 -fno-stack-protector -march=armv8-a test.c -o test
test.c:4:10: fatal error: cpuid.h: No such file or directory
4 | #include <cpuid.h>
| ^~~~~~~~~
compilation terminated.
The contents of test.c:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <cpuid.h>
// Requires that the user input the CPUID,
// plus the bytes "3" and "Q";
void succeed(char* string) {
printf("Yes, %s is correct!\n", string);
exit(0);
}
void fail(char* string) {
printf("No, %s is not correct.\n", string);
exit(1);
}
void shift_int_to_char(int i, char* buff) {
buff[0] = (i) & 0xFF;
buff[1] = (i >> 8) & 0xFF;
buff[2] = (i >> 16) & 0xFF;
buff[3] = (i >> 24) & 0xFF;
}
int main(int argc, char** argv) {
if (argc != 2) {
printf("Need exactly one argument.\n");
return -1;
}
unsigned int eax, ebx, ecx, edx;
char* buff = malloc(sizeof(char) * 15);
__get_cpuid(0, &eax, &ebx, &ecx, &edx);
shift_int_to_char(ebx, buff);
shift_int_to_char(edx, buff + 4);
shift_int_to_char(ecx, buff + 8);
buff[12] = '3';
buff[13] = 'Q';
buff[14] = '\0';
int correct = (strcmp(buff, argv[1]) == 0);
free(buff);
if (correct) {
succeed(argv[1]);
} else {
fail(argv[1]);
}
}
As explained in the comments, you need to port this program to the Aarch64 architecture, you cannot just compile the code as is. The features implemented in your SoC are exposed through the various AArch64 feature system registers or instruction set attribute registers, for example ID_AA64PFR1_EL1.
By convention, registers with names terminating with _EL1 cannot be accessed from a user-mode program running at EL0. Some support in the Operating System (running at EL1) is therefore required for reading those registers from a user-mode program - I used a Linux 5.15.0 kernel for the purpose of this answer.
On a recent Linux kernel, this can be achieved by using the HWCAP_CPUID API available in hwcaps - more details in this article , ARM64 CPU Feature Registers.
We can compile/execute the example code provided in Appendix I:
/*
* Sample program to demonstrate the MRS emulation ABI.
*
* Copyright (C) 2015-2016, ARM Ltd
*
* Author: Suzuki K Poulose <suzuki.poulose#arm.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program 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.
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program 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.
*/
#include <asm/hwcap.h>
#include <stdio.h>
#include <sys/auxv.h>
#define get_cpu_ftr(id) ({ \
unsigned long __val; \
asm("mrs %0, "#id : "=r" (__val)); \
printf("%-20s: 0x%016lx\n", #id, __val); \
})
int main(void)
{
if (!(getauxval(AT_HWCAP) & HWCAP_CPUID)) {
fputs("CPUID registers unavailable\n", stderr);
return 1;
}
get_cpu_ftr(ID_AA64ISAR0_EL1);
get_cpu_ftr(ID_AA64ISAR1_EL1);
get_cpu_ftr(ID_AA64MMFR0_EL1);
get_cpu_ftr(ID_AA64MMFR1_EL1);
get_cpu_ftr(ID_AA64PFR0_EL1);
get_cpu_ftr(ID_AA64PFR1_EL1);
get_cpu_ftr(ID_AA64DFR0_EL1);
get_cpu_ftr(ID_AA64DFR1_EL1);
get_cpu_ftr(MIDR_EL1);
get_cpu_ftr(MPIDR_EL1);
get_cpu_ftr(REVIDR_EL1);
#if 0
/* Unexposed register access causes SIGILL */
get_cpu_ftr(ID_MMFR0_EL1);
#endif
return 0;
}
Execution:
gcc -o cpu-feature-registers cpu-feature-registers.c
./cpu-feature-registers
ID_AA64ISAR0_EL1 : 0x0000000000010000
ID_AA64ISAR1_EL1 : 0x0000000000000000
ID_AA64MMFR0_EL1 : 0x00000111ff000000
ID_AA64MMFR1_EL1 : 0x0000000000000000
ID_AA64PFR0_EL1 : 0x0000000000000011
ID_AA64PFR1_EL1 : 0x0000000000000000
ID_AA64DFR0_EL1 : 0x0000000000000006
ID_AA64DFR1_EL1 : 0x0000000000000000
MIDR_EL1 : 0x00000000410fd034
MPIDR_EL1 : 0x0000000080000000
REVIDR_EL1 : 0x0000000000000000
You now just need to identify the feature/instructions set register(s) that will provide you with the same kind of information provided by your x86_64 code.
Please let me know what are the _nocancel() system calls (e.g. __pwrite_nocancel(), and whether there is a way to create an LD_PRELOAD library to intercept those calls. Here's a bit of the background:
I'm investigating functionality of an Oracle database, and would like to add a small shim layer using LD_PRELOAD to capture information about the call in user space. I'm aware of other ways of capturing this information using system tap, but using LD_PRELOAD is a hard requirement from the customer.
strace shows that this particular process is calling pwrite() repeatedly; similarly, the pstack stack trace shows that __pwrite_nocancel() is being called as the last entry on the stack. I tried reproducing my own __libc_pwrite() function, and declaring
extern ssize_t pwrite(int fd, const void *buf, size_t numBytes, off_t offset)__attribute__((weak, alias ( "__libc_pwrite")));
but when I link the library and run nm -a |grep pwrite, I get this:
000000000006c190 T __libc_pwrite
000000000006c190 W pwrite
in contrast, nm -a /lib64/libpthread.so.0 |grep pwrite gives the following:
000000000000eaf0 t __libc_pwrite
000000000000eaf0 t __libc_pwrite64
000000000000eaf0 W pwrite
000000000000eaf0 t __pwrite
000000000000eaf0 W pwrite64
000000000000eaf0 W __pwrite64
0000000000000000 a pwrite64.c
000000000000eaf9 t __pwrite_nocancel
I've noticed that the _nocancel version is only 9 bytes ahead of the __pwrite, but looking at the source code, I'm unsure as to where it's being created:
/* Copyright (C) 1997, 1998, 2000, 2002, 2003, 2004, 2006
Free Software Foundation, Inc.
This file is part of the GNU C Library.
Contributed by Ulrich Drepper <drepper#cygnus.com>, 1997.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>. */
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <endian.h>
#include <sysdep-cancel.h>
#include <sys/syscall.h>
#include <bp-checks.h>
#include <kernel-features.h>
#ifdef __NR_pwrite64 /* Newer kernels renamed but it's the same. */
# ifdef __NR_pwrite
# error "__NR_pwrite and __NR_pwrite64 both defined???"
# endif
# define __NR_pwrite __NR_pwrite64
#endif
#if defined __NR_pwrite || __ASSUME_PWRITE_SYSCALL > 0
# if __ASSUME_PWRITE_SYSCALL == 0
static ssize_t __emulate_pwrite (int fd, const void *buf, size_t count,
off_t offset) internal_function;
# endif
ssize_t
__libc_pwrite (fd, buf, count, offset)
int fd;
const void *buf;
size_t count;
off_t offset;
{
ssize_t result;
if (SINGLE_THREAD_P)
{
/* First try the syscall. */
result = INLINE_SYSCALL (pwrite, 6, fd, CHECK_N (buf, count), count, 0,
__LONG_LONG_PAIR (offset >> 31, offset));
# if __ASSUME_PWRITE_SYSCALL == 0
if (result == -1 && errno == ENOSYS)
/* No system call available. Use the emulation. */
result = __emulate_pwrite (fd, buf, count, offset);
# endif
return result;
}
int oldtype = LIBC_CANCEL_ASYNC ();
/* First try the syscall. */
result = INLINE_SYSCALL (pwrite, 6, fd, CHECK_N (buf, count), count, 0,
__LONG_LONG_PAIR (offset >> 31, offset));
# if __ASSUME_PWRITE_SYSCALL == 0
if (result == -1 && errno == ENOSYS)
/* No system call available. Use the emulation. */
result = __emulate_pwrite (fd, buf, count, offset);
# endif
LIBC_CANCEL_RESET (oldtype);
return result;
}
strong_alias (__libc_pwrite, __pwrite)
weak_alias (__libc_pwrite, pwrite)
# define __libc_pwrite(fd, buf, count, offset) \
static internal_function __emulate_pwrite (fd, buf, count, offset)
#endif
#if __ASSUME_PWRITE_SYSCALL == 0
# include <sysdeps/posix/pwrite.c>
#endif
Any help is appreciated.
The pwrite_nocancel() et cetera are not syscalls in Linux. They are functions internal to the C library, and tightly coupled with pthreads and thread cancellation.
The _nocancel() versions behave exactly the same as the original functions, except that these versions are not thread cancellation points.
Most I/O functions are cancellation points. That is, if the thread cancellation type is deferred and cancellation state is enabled, and another thread in the process has requested the thread to be cancelled, the thread will cancel (exit) when entering a cancellation point. See man 3 pthread_cancel, man 3 pthread_setcancelstate, and man 3 pthread_setcanceltype for further details.
Unfortunately, the pwrite_nocancel() and other _nocancel() functions are internal (local) to the pthreads library, and as such, are quite difficult to interpose; they're not dynamic symbols, so the dynamic linker cannot override them. At this point, I suspect, but am not sure, that the way to interpose them would involve rewriting the beginning of the library code with a direct jump to your own code.
If they were exported (global) functions, they could be interposed just like any other library function (these are provided by the pthread library, libpthread), using the normal approach. (Of my own answers here, you might find this, this, and this informative. Otherwise, just search for LD_PRELOAD example.)
Given that you want to interpose via LD_PRELOAD, you need to look at the same symbols rtld is using. Therefore, instead of nm -a, use objdump -T. If the symbol you want is not listed there, you cannot interpose it. All exported glibc symbols have a "version" string that is compared by rtld and allows having multiple versions of the same symbol (this is why even nm -D is not useful -- it doesn't show the versions). To interpose something, you should have the same version string. This can be done using a version script (see info ld), the .symver asm directive or a macro that invokes that directive. Note that symbols with a "private" version may change at any new revision of the libc, and that other symbols may be superseded by new ones at any new version (the symbols still work, but applications compiled against the new version don't use them anymore).
I am experiencing a strange problem with the the popen and fgets library functions on a Linux system.
A short program demonstrating the problem is below that:
Installs a signal handler for SIGUSR1.
Creates a secondary thread to repeatedly send SIGUSR1 to the main thread.
In the main thread, repeatedly executes a very simple shell command via popen(), gets the output via fgets(), and checks to see if the output is of the expected length.
The output is unexpectedly truncated intermittently. Why?
Command-line invocation example:
$ gcc -Wall test.c -lpthread && ./a.out
iteration 0
iteration 1
iteration 2
iteration 3
iteration 4
iteration 5
unexpected length: 0
Details of my machine (the program will also compile and run with this online C compiler):
$ cat /etc/redhat-release
CentOS release 6.5 (Final)
$ uname -a
Linux localhost.localdomain 2.6.32-431.17.1.el6.x86_64 #1 SMP Wed May 7 23:32:49 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
# gcc 4.4.7
$ gcc --version
gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-4)
Copyright (C) 2010 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# glibc 2.12
$ ldd --version
ldd (GNU libc) 2.12
Copyright (C) 2010 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.
The program:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <pthread.h>
#include <errno.h>
void dummy_signal_handler(int signal);
void* signal_spam_task(void* arg);
void echo_and_verify_output();
char* fgets_with_retry(char *buffer, int size, FILE *stream);
static pthread_t main_thread;
/**
* Prints an error message and exits if the output is truncated, which happens
* about 5% of the time.
*
* Installing the signal handler with the SA_RESTART flag, blocking SIGUSR1
* during the call to fgets(), or sleeping for a few milliseconds after the
* call to popen() will completely prevent truncation.
*/
int main(int argc, char **argv) {
// install signal handler for SIGUSR1
struct sigaction sa, osa;
sa.sa_handler = dummy_signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGUSR1, &sa, &osa);
// create a secondary thread to repeatedly send SIGUSR1 to main thread
main_thread = pthread_self();
pthread_t spam_thread;
pthread_create(&spam_thread, NULL, signal_spam_task, NULL);
// repeatedly execute simple shell command until output is unexpected
unsigned int i = 0;
for (;;) {
printf("iteration %u\n", i++);
echo_and_verify_output();
}
return 0;
}
void dummy_signal_handler(int signal) {}
void* signal_spam_task(void* arg) {
for (;;)
pthread_kill(main_thread, SIGUSR1);
return NULL;
}
void echo_and_verify_output() {
// run simple command
FILE* stream = popen("echo -n hello", "r");
if (!stream)
exit(1);
// count the number of characters in the output
unsigned int length = 0;
char buffer[BUFSIZ];
while (fgets_with_retry(buffer, BUFSIZ, stream) != NULL)
length += strlen(buffer);
if (ferror(stream) || pclose(stream))
exit(1);
// double-check the output
if (length != strlen("hello")) {
printf("unexpected length: %i\n", length);
exit(2);
}
}
// version of fgets() that retries on EINTR
char* fgets_with_retry(char *buffer, int size, FILE *stream) {
for (;;) {
if (fgets(buffer, size, stream))
return buffer;
if (feof(stream))
return NULL;
if (errno != EINTR)
exit(1);
clearerr(stream);
}
}
If an error occurs on a FILE stream while reading with fgets, it's undefined as to whether some bytes read are transferred to the buffer before fgets returns NULL or not (7.19.7.2 of the C99 spec). So if the SIGUSR1 signal occurs while in the fgets call and causes an EINTR, its possible that some characters may be lost from the stream.
The upshot is that you can't use stdio functions to read/write FILE objects if the underlying system calls might have recoverable error returns (such as EINTR or EAGAIN), as there's no guarantee the standard library won't lose some data from the buffer when that happens. You can claim that this is a "bug" in the standard library implementation, but it is a bug that the C standard allows.
I have to check Linux system information. I can execute system commands in C, but doing so I create a new process for every one, which is pretty expensive. I was wondering if there is a way to obtain system information without being forced to execute a shell command. I've been looking around for a while and I found nothing. Actually, I'm not even sure if it's more convenient to execute commands via Bash calling them from my C program or find a way to accomplish the tasks using only C.
Linux exposes a lot of information under /proc. You can read the data from there. For example, fopen the file at /proc/cpuinfo and read its contents.
A presumably less known (and more complicated) way to do that, is that you can also use the api interface to sysctl. To use it under Linux, you need to #include <unistd.h>, #include <linux/sysctl.h>. A code example of that is available in the man page:
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <linux/sysctl.h>
int _sysctl(struct __sysctl_args *args );
#define OSNAMESZ 100
int
main(void)
{
struct __sysctl_args args;
char osname[OSNAMESZ];
size_t osnamelth;
int name[] = { CTL_KERN, KERN_OSTYPE };
memset(&args, 0, sizeof(struct __sysctl_args));
args.name = name;
args.nlen = sizeof(name)/sizeof(name[0]);
args.oldval = osname;
args.oldlenp = &osnamelth;
osnamelth = sizeof(osname);
if (syscall(SYS__sysctl, &args) == -1) {
perror("_sysctl");
exit(EXIT_FAILURE);
}
printf("This machine is running %*s\n", osnamelth, osname);
exit(EXIT_SUCCESS);
}
However, the man page linked also notes:
Glibc does not provide a wrapper for this system call; call it using
syscall(2). Or rather... don't call it: use of this system call has
long been discouraged, and it is so unloved that it is likely to
disappear in a future kernel version. Since Linux 2.6.24, uses of this
system call result in warnings in the kernel log. Remove it from your
programs now; use the /proc/sys interface instead.
This system call is available only if the kernel was configured with
the CONFIG_SYSCTL_SYSCALL option.
Please keep in mind that anything you can do with sysctl(), you can also just read() from /proc/sys. Also note that I do understand that the usefulness of that syscall is questionable, I just put it here for reference.
You can also use the sys/utsname.h header file to get the kernel version, hostname, operating system, machine hardware name, etc. More about sys/utsname.h is here. This is an example of getting the current kernel release.
#include <stdio.h> // I/O
#include <sys/utsname.h>
int main(int argc, char const *argv[])
{
struct utsname buff;
printf("Kernel Release = %s\n", buff.release); // kernel release
return 0;
}
This is the same as using the uname command. You can also use the -a option which stands for all information.
uname -r # -r stands for kernel release
I am trying to build a program in C which has a lot of optional features that depend on various shared libraries.
In our heterogeneous computing cluster not all of those libraries are available (or up to date) on all systems.
Examples are symbols from newer glibc (sched_getcpu##GLIBC_2.6, __sched_cpucount##GLIBC_2.6) or whole shared libraries which may or may not be available (libnuma, libR, libpbs).
I know that I can use libdl to load the symbols with dlopen and dlsym, but doing this for an ever growing number of symbols (around 30 at the moment) is tedious at best.
As far as I understand shared libraries in Linux are lazy-loaded by default, so a symbol should not be needed until it is actually used.
But if I try to check for that in advance then it fails at execution start:
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <dlfcn.h>
#include <sched.h>
int main() {
void *lib_handle;
int (*fn)(void);
int x;
char *error;
lib_handle = dlopen("libc.so.6", RTLD_LAZY);
if (!lib_handle)
{
fprintf(stderr, "%s\n", dlerror());
exit(1);
}
fn = dlsym(lib_handle, "sched_getcpu");
if ((error = dlerror()) != NULL)
{
fprintf(stderr, "%s\n", error);
exit(1);
}
printf("%d\n", sched_getcpu());
return 0;
}
On compile system which has all libraries:
$ icc test.c
$ ./a.out
10
On another system which has a less recent version of GLIBC:
$ ./a.out
./a.out: /lib64/libc.so.6: version `GLIBC_2.6' not found (required by ./a.out)
If I comment out the line that actually calls sched_getcpu then I get instead on the lesser system:
$ ./a.out
/lib64/libc.so.6: undefined symbol: sched_getcpu
So, is there a way to force libraries only to be loaded on use and have checks like these before blocks that use them?
Not with glibc. This is a fail-safe and it's in place so that you won't shoot yourself in the foot. If the GLIBC_2.6 symbol wasn't defined and looked up, even if there were no other missing symbols, you could get garbage results from glibc (data corruption and crashes,) since it's not forwards compatible.
If you need compatibility on the glibc level, you need to build against the lowest common version.