Fixed
Created: Sep 3, 2014
Updated: Dec 3, 2018
Resolved Date: Sep 10, 2014
Found In Version: 6.0
Fix Version: 6.0.0.12
Severity: Standard
Applicable for: Wind River Linux 6
Component/s: Kernel
Userspace setns() function takes too long by design.
I measured the speed of the setns() function, using clock_gettime(CLOCK_MONOTONIC) to get the time before and after a loop that calls setns() repeatedly. Averaged over 10,000 calls, one call to setns() takes 5 milliseconds.
The kernel sequence that I think is eating the time is:
void switch_task_namespaces(struct task_struct *p, struct nsproxy *new)
{
struct nsproxy *ns;
might_sleep();
ns = p->nsproxy;
rcu_assign_pointer(p->nsproxy, new);
if (ns && atomic_dec_and_test(&ns->count)) {
/*
* wait for others to get what they want from this nsproxy.
*
* cannot release this nsproxy via the call_rcu() since
* put_mnt_ns() will want to sleep
*/
synchronize_rcu();
free_nsproxy(ns);
}
}
Specifically, the call to synchronize_rcu(), which runs a synchronization protocol that is intended to be optimized for multiple CPUs. From the documentation for the Read-Copy-Update synchronization protocol, the call will wait for one “grace period”, which seems to account for the 5 milliseconds.
On a single-CPU build, there is no other CPU to synchronize with, so setns() probably takes less time. I currently do not have a single-CPU VM to test this on.
Clearly, we need to cut the number of calls to setns() to an absolute minimum.
Export wrl5 SDK, build this code:
user@build-host:~/test$ $CC setns_test.c -lrt
user@build-host:~/test$ cat setns_test.c
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
static int diff(struct timespec start, struct timespec end)
{
struct timespec temp;
if ((end.tv_nsec-start.tv_nsec)<0) {
temp.tv_sec = end.tv_sec-start.tv_sec-1;
temp.tv_nsec = 1000000000+end.tv_nsec-start.tv_nsec;
} else {
temp.tv_sec = end.tv_sec-start.tv_sec;
temp.tv_nsec = end.tv_nsec-start.tv_nsec;
}
printf("diff: %ld,%ld\n", temp.tv_sec, temp.tv_nsec);
return 0;
}
int main(int argc, char *argv[])
{
int fd, err, i;
struct timespec time1, time2;
fd = open("/proc/self/ns/net", O_RDONLY);
if (fd == -1) {
perror("open");
return -1;
}
clock_gettime(CLOCK_MONOTONIC, &time1);
for (i=0; i<10000; i++) {
err = setns(fd, 0);
if (err) {
perror("setns");
return -1;
}
}
clock_gettime(CLOCK_MONOTONIC, &time2);
close(fd);
diff(time1, time2);
return 0;
}
Copy it to a MATXM and run it:
root@localhost:~# time /tmp/a.out
diff: 50,89477037
real 0m50.096s
user 0m0.000s
sys 0m0.099s