From: Alex Belits <hidden> Date: 2020-03-04 16:02:39
This is an update of task isolation work that was originally done by
Chris Metcalf [off-list ref] and maintained by him until
November 2017. It is adapted to the current kernel and cleaned up to
make this functionality both more complete (as in, prevent isolation
breaking in situations that were not covered before) and cleaner (as
in, avoid any dubious or fragile use of kernel interfaces, and provide
clean and reliable isolation breaking procedure).
I guess, I have to explain why such a thing exists.
This is the result of development and maintenance of task isolation
functionality that originally started based on task isolation patch
v15 and was later updated to include v16. It provided RTOS-like
predictable environment for userspace tasks running on arm64
processors alongside with full-featured Linux environment. It is
intended to provide reliable interruption-free environment from the
point when a userspace task enters isolation and until the moment it
leaves isolation or receives a signal intentionally sent to it, and
was successfully used for this purpose. While CPU isolation with nohz
provides an environment that is close to this requirement, the
remaining IPIs and other disturbances keep it from being usable for
tasks that require complete predictability of CPU timing.
It is clear that such isolation is neither possible nor necessary
while a CPU is running kernel, userspace initialization or cleanup, so
there is a need for a separate isolated state that a userspace task
can enter and exit. This was the reason for using the original task
isolation, and such reason still exists now. The alternative, running
RTOS instead of Linux, is becoming more and more labor-consuming
because modern CPUs and SoCs have very complex device/resource
configuration and management procedures, and at this point for some
hardware it is clearly in the realm of impractical to maintain an RTOS
with hardware support on par with Linux kernel, reliable and secure at
the same time.
On the other hand, development of modern embedded-oriented SoCs had
shown that numerous CPU cores may or may not share any hardware
resources based on SoC designers' intention. Therefore OS ability to
switch a CPU core into RTOS-ish mode and truly, really, at all levels,
leave it alone until OS is needed there again, is an important feature
for modern embedded systems development. Probably more important than
even real-time interrupts latency and preemption, now that people,
when they don't like how their interrupts are handled, can just add
CPU cores. This is why we had to maintain task isolation, and I
believe, after all improvements in CPU isolation, timer and interrupt
management that was done in Linux since 2017, it is needed even more,
as opposed to less.
This set of patches only covers the implementation of task isolation,
however the need for additional functionality, such as selective TLB
flushes, is one of the reasons behind task_isolation_on_cpu() avoiding
any non-isolation-specific data structures and the existence of
fast_task_isolation_cpu_cleanup() function, that is always called on
the CPU where isolated task is running.
Reporting task isolation breaking in kernel log is now more
informative and, if necessary, can be adapted to provide meaningful
cause information to userspace software. I am not sure if such
mechanism is needed -- development and reporting failures in
production usually relies on kernel logs, and in production it is
assumed that isolation breaking should not happen on its own. On the
other hand, if application can collect a meaningful log where its
events are matched to isolation failures, this may be better for
developers than matching timing of records from multiple sources. For
now, only log shows detailed descriptions.
The userspace support and test program is now at
https://github.com/abelits/libtmc . It was originally developed for
earlier implementation, so it has some checks that may be redundant
now but kept for compatibility.
My thanks to Chris Metcalf for design and maintenance of the original
task isolation patch, Francis Giraldeau [off-list ref]
and Yuri Norov [off-list ref] for various contributions to this
work, and Frederic Weisbecker [off-list ref] for his work on
CPU isolation and housekeeping that made possible to remove some less
elegant solutions that I had to devise for earlier, <4.17 kernels.
The previous patch (v16 by Chris Metcalf) is at:
https://lore.kernel.org/lkml/1509728692-10460-1-git-send-email-cmetcalf@mellanox.com
--
Alex
From: Alex Belits <hidden> Date: 2020-03-04 16:04:01
From: Chris Metcalf <redacted>
In commit f01f17d3705b ("mm, vmstat: make quiet_vmstat lighter")
the quiet_vmstat() function became asynchronous, in the sense that
the vmstat work was still scheduled to run on the core when the
function returned. For task isolation, we need a synchronous
version of the function that guarantees that the vmstat worker
will not run on the core on return from the function. Add a
quiet_vmstat_sync() function with that semantic.
Signed-off-by: Alex Belits <redacted>
---
include/linux/vmstat.h | 2 ++
mm/vmstat.c | 9 +++++++++
2 files changed, 11 insertions(+)
From: Alex Belits <hidden> Date: 2020-03-04 16:05:08
From: Chris Metcalf <redacted>
This function checks to see if a vmstat worker is not running,
and the vmstat diffs don't require an update. The function is
called from the task-isolation code to see if we need to
actually do some work to quiet vmstat.
Signed-off-by: Alex Belits <redacted>
---
include/linux/vmstat.h | 2 ++
mm/vmstat.c | 10 ++++++++++
2 files changed, 12 insertions(+)
From: Alex Belits <hidden> Date: 2020-03-04 16:08:03
The existing nohz_full mode is designed as a "soft" isolation mode
that makes tradeoffs to minimize userspace interruptions while
still attempting to avoid overheads in the kernel entry/exit path,
to provide 100% kernel semantics, etc.
However, some applications require a "hard" commitment from the
kernel to avoid interruptions, in particular userspace device driver
style applications, such as high-speed networking code.
This change introduces a framework to allow applications
to elect to have the "hard" semantics as needed, specifying
prctl(PR_TASK_ISOLATION, PR_TASK_ISOLATION_ENABLE) to do so.
The kernel must be built with the new TASK_ISOLATION Kconfig flag
to enable this mode, and the kernel booted with an appropriate
"isolcpus=nohz,domain,CPULIST" boot argument to enable
nohz_full and isolcpus. The "task_isolation" state is then indicated
by setting a new task struct field, task_isolation_flag, to the
value passed by prctl(), and also setting a TIF_TASK_ISOLATION
bit in the thread_info flags. When the kernel is returning to
userspace from the prctl() call and sees TIF_TASK_ISOLATION set,
it calls the new task_isolation_start() routine to arrange for
the task to avoid being interrupted in the future.
With interrupts disabled, task_isolation_start() ensures that kernel
subsystems that might cause a future interrupt are quiesced. If it
doesn't succeed, it adjusts the syscall return value to indicate that
fact, and userspace can retry as desired. In addition to stopping
the scheduler tick, the code takes any actions that might avoid
a future interrupt to the core, such as a worker thread being
scheduled that could be quiesced now (e.g. the vmstat worker)
or a future IPI to the core to clean up some state that could be
cleaned up now (e.g. the mm lru per-cpu cache).
Once the task has returned to userspace after issuing the prctl(),
if it enters the kernel again via system call, page fault, or any
other exception or irq, the kernel will kill it with SIGKILL.
In addition to sending a signal, the code supports a kernel
command-line "task_isolation_debug" flag which causes a stack
backtrace to be generated whenever a task loses isolation.
To allow the state to be entered and exited, the syscall checking
test ignores the prctl(PR_TASK_ISOLATION) syscall so that we can
clear the bit again later, and ignores exit/exit_group to allow
exiting the task without a pointless signal being delivered.
The prctl() API allows for specifying a signal number to use instead
of the default SIGKILL, to allow for catching the notification
signal; for example, in a production environment, it might be
helpful to log information to the application logging mechanism
before exiting. Or, the signal handler might choose to reset the
program counter back to the code segment intended to be run isolated
via prctl() to continue execution.
In a number of cases we can tell on a remote cpu that we are
going to be interrupting the cpu, e.g. via an IPI or a TLB flush.
In that case we generate the diagnostic (and optional stack dump)
on the remote core to be able to deliver better diagnostics.
If the interrupt is not something caught by Linux (e.g. a
hypervisor interrupt) we can also request a reschedule IPI to
be sent to the remote core so it can be sure to generate a
signal to notify the process.
Separate patches that follow provide these changes for x86, arm,
and arm64.
Signed-off-by: Alex Belits <redacted>
---
.../admin-guide/kernel-parameters.txt | 6 +
include/linux/hrtimer.h | 4 +
include/linux/isolation.h | 229 ++++++
include/linux/sched.h | 4 +
include/linux/tick.h | 3 +
include/uapi/linux/prctl.h | 6 +
init/Kconfig | 28 +
kernel/Makefile | 2 +
kernel/context_tracking.c | 2 +
kernel/isolation.c | 774 ++++++++++++++++++
kernel/signal.c | 2 +
kernel/sys.c | 6 +
kernel/time/hrtimer.c | 27 +
kernel/time/tick-sched.c | 18 +
14 files changed, 1111 insertions(+)
create mode 100644 include/linux/isolation.h
create mode 100644 kernel/isolation.c
@@ -4808,6 +4808,12 @@ neutralize any effect of /proc/sys/kernel/sysrq. Useful for debugging.+ task_isolation_debug [KNL]+ In kernels built with CONFIG_TASK_ISOLATION, this+ setting will generate console backtraces to+ accompany the diagnostics generated about+ interrupting tasks running with task isolation.+ tcpmhash_entries= [KNL,NET] Set the number of tcp_metrics_hash slots. Default value is 8192 or 16384 depending on total
@@ -576,6 +576,34 @@ config CPU_ISOLATIONsource"kernel/rcu/Kconfig"+configHAVE_ARCH_TASK_ISOLATION+bool++configTASK_ISOLATION+bool"Provide hard CPU isolation from the kernel on demand"+depends onNO_HZ_FULL&&HAVE_ARCH_TASK_ISOLATION+help++Allowuserspaceprocessesthatplacethemselvesoncoreswith+nohz_fullandisolcpusenabled,andrunprctl(PR_TASK_ISOLATION),+to"isolate"themselvesfromthekernel.Priortoreturningto+userspace,isolatedtaskswillarrangethatnofuturekernel+activitywillinterruptthetaskwhilethetaskisrunningin+userspace.Attemptingtore-enterthekernelwhileinthismode+willcausethetasktobeterminatedwithasignal;youmust+explicitlyuseprctl()todisabletaskisolationbeforeresuming+normaluseofthekernel.++This"hard"isolationfromthekernelisrequiredforuserspace+tasksthatarerunninghardreal-timetasksinuserspace,suchas+ahigh-speednetworkdriverinuserspace.Withoutthisoption,but+withNO_HZ_FULLenabled,thekernelwillmakeabest-faith,"soft"+efforttoshieldasingleuserspaceprocessfrominterrupts,but+makesnoguarantees.++Youshouldsay"N"unlessyouareintendingtoruna+high-performanceuserspacedriverorsimilartask.+configBUILD_BIN2Cbooldefaultn
@@ -0,0 +1,774 @@+// SPDX-License-Identifier: GPL-2.0-only+/*+*linux/kernel/isolation.c+*+*Implementationoftaskisolation.+*+*Authors:+*ChrisMetcalf<cmetcalf@mellanox.com>+*AlexBelits<abelits@marvell.com>+*YuriNorov<ynorov@marvell.com>+*/++#include<linux/mm.h>+#include<linux/swap.h>+#include<linux/vmstat.h>+#include<linux/sched.h>+#include<linux/isolation.h>+#include<linux/syscalls.h>+#include<linux/smp.h>+#include<linux/tick.h>+#include<asm/unistd.h>+#include<asm/syscall.h>+#include<linux/hrtimer.h>++/*+*Thesevaluesarestoredintask_isolation_state.+*NotethatSTATE_NORMAL+TIF_TASK_ISOLATIONmeanswearestill+*returningfromsys_prctl()touserspace.+*/+enum{+STATE_NORMAL=0,/* Not isolated */+STATE_ISOLATED=1/* In userspace, isolated */+};++/*+*Thisvariablecontainsthreadflagscopiedatthemoment+*whenschedule()switchedtothetaskonagivenCPU,+*or0ifnotaskisrunning.+*/+DEFINE_PER_CPU(unsignedlong,tsk_thread_flags_cache);++/*+*CounterforisolationstateonagivenCPU,incrementswhenentering+*isolationanddecrementswhenexitingisolation(beforeorafterthe+*cleanup).Multiplesimultaneouslyrunningproceduresenteringor+*exitingisolationarepreventedbycheckingtheresultof+*incrementingordecrementingthisvariable.Thisvariableisboth+*incrementedanddecrementedbyCPUthatcausedisolationenteringor+*exit.+*+*Thisisnecessarybecausemultipleisolation-breakingeventsmayhappen+*atonce(oroneastheresultoftheother),howeverisolationexit+*mayonlyhappenoncetotransitionfromisolatedtonon-isolatedstate.+*Therefore,ifdecrementingthiscounterresultsinavaluelessthan0,+*isolationexitprocedurecan'tbestarted--italreadyhappened,oris+*inprogress,orisolationisnotenteredyet.+*/+DEFINE_PER_CPU(atomic_t,isol_counter);++/*+*DescriptionofthelasttwotasksthatranisolatedonagivenCPU.+*Thisisintendedonlyformessagesaboutisolationbreaking.We+*don'twantanyreferencestoactualtaskwhileaccessingthisfrom+*CPUthatcausedisolationbreaking--weknownothingabouttiming+*anddon'twanttouselockingorRCU.+*/+structisol_task_desc{+atomic_tcurr_index;+atomic_tcurr_index_wr;+boolwarned[2];+pid_tpid[2];+pid_ttgid[2];+charcomm[2][TASK_COMM_LEN];+};+staticDEFINE_PER_CPU(structisol_task_desc,isol_task_descs);++/*+*Counterforisolationexitingprocedures(fromrequesttothestartof+*cleanup)beingattemptedatonceonaCPU.Normallyincrementingof+*thiscounterisperformedfromtheCPUthatcausedisolationbreaking,+*howeverdecrementingisdonefromthecleanupprocedure,delegatedto+*theCPUthatisexitingisolation,notfromtheCPUthatcausedisolation+*breaking.+*+*Ifincrementingthiscounterwhilestartingisolationexitprocedure+*resultsinavaluegreaterthan0,isolationexitingisalreadyin+*progress,andcleanupdidnotstartyet.Thismeans,countershouldbe+*decrementedback,andisolationexitthatisalreadyinprogress,should+*beallowedtocomplete.Otherwise,anewisolationexitprocedureshould+*bestarted.+*/+DEFINE_PER_CPU(atomic_t,isol_exit_counter);++/*+*Descriptorforisolation-breakingSMPcalls+*/+DEFINE_PER_CPU(call_single_data_t,isol_break_csd);++cpumask_var_ttask_isolation_map;+cpumask_var_ttask_isolation_cleanup_map;+staticDEFINE_SPINLOCK(task_isolation_cleanup_lock);++/* We can run on cpus that are isolated from the scheduler and are nohz_full. */+staticint__inittask_isolation_init(void)+{+alloc_bootmem_cpumask_var(&task_isolation_cleanup_map);+if(alloc_cpumask_var(&task_isolation_map,GFP_KERNEL))+/*+*Atthispointtaskisolationshouldmatch+*nohz_full.Thismaychangeinthefuture.+*/+cpumask_copy(task_isolation_map,tick_nohz_full_mask);+return0;+}+core_initcall(task_isolation_init)++/* Enable stack backtraces of any interrupts of task_isolation cores. */+staticbooltask_isolation_debug;+staticint__inittask_isolation_debug_func(char*str)+{+task_isolation_debug=true;+return1;+}+__setup("task_isolation_debug",task_isolation_debug_func);++/*+*Recordname,pidandgrouppidofthetaskenteringisolationon+*thecurrentCPU.+*/+staticvoidrecord_curr_isolated_task(void)+{+intind;+intcpu=smp_processor_id();+structisol_task_desc*desc=&per_cpu(isol_task_descs,cpu);+structtask_struct*task=current;++/* Finish everything before recording current task */+smp_mb();+ind=atomic_inc_return(&desc->curr_index_wr)&1;+desc->comm[ind][sizeof(task->comm)-1]='\0';+memcpy(desc->comm[ind],task->comm,sizeof(task->comm)-1);+desc->pid[ind]=task->pid;+desc->tgid[ind]=task->tgid;+desc->warned[ind]=false;+/* Write everything, to be seen by other CPUs */+smp_mb();+atomic_inc(&desc->curr_index);+/* Everyone will see the new record from this point */+smp_mb();+}++/*+*Printmessageprefixedwiththedescriptionofthecurrent(or+*last)isolatedtaskonagivenCPU.Intendedforisolationbreaking+*messagesthatincludetargettaskfortheuser'sconvenience.+*+*Messagesproducedwiththisfunctionmayhaveobsoletetask+*informationifisolatedtasksmanagedtoexit,startandenter+*isolationmultipletimes,ormultipletaskstriedtoenter+*isolationonthesameCPUatonce.Forthoseunusualcasesitwould+*containavaliddescriptionofthecauseforisolationbreakingand+*targetCPUnumber,justnotthecorrectdescriptionofwhichtask+*endeduplosingisolation.+*/+inttask_isolation_message(intcpu,intlevel,boolsupp,constchar*fmt,...)+{+structisol_task_desc*desc;+structtask_struct*task;+va_listargs;+charbuf_prefix[TASK_COMM_LEN+20+3*20];+charbuf[200];+intcurr_cpu,ind_counter,ind_counter_old,ind;++curr_cpu=get_cpu();+desc=&per_cpu(isol_task_descs,cpu);+ind_counter=atomic_read(&desc->curr_index);++if(curr_cpu==cpu){+/*+*MessageisforthecurrentCPUsocurrent+*task_structshouldbeusedinsteadofcached+*information.+*+*Likeinotherdiagnosticmessages,ifissuedfrom+*interruptcontext,currentwillbetheinterrupted+*task.Unlikeotherdiagnosticmessages,thisis+*alwaysrelevantbecausethemessageisabout+*interruptingatask.+*/+ind=ind_counter&1;+if(supp&&desc->warned[ind]){+/*+*Ifsuppistrue,skipthemessageifthe+*sametaskwasmentionedinthemessage+*originatedonremoteCPU,anditdidnot+*re-enterisolatedstatesincethen(warned+*istrue).Onlylocalmessagesfollowing+*remotemessages,likelyaboutthesame+*isolationbreakingevent,areskippedto+*avoidduplication.Ifremotecauseis+*immediatelyfollowedbyalocalonebefore+*isolationisbroken,localcauseisskipped+*frommessages.+*/+put_cpu();+return0;+}+task=current;+snprintf(buf_prefix,sizeof(buf_prefix),+"isolation %s/%d/%d (cpu %d)",+task->comm,task->tgid,task->pid,cpu);+put_cpu();+}else{+/*+*MessageisforremoteCPU,usecachedinformation.+*/+put_cpu();+/*+*Makesure,indexremainedunchangedwhiledatawas+*copied.Ifitchanged,datathatwascopiedmaybe+*inconsistentbecausetwoupdatesinasequencecould+*overwritethedatawhileitwasbeingread.+*/+do{+/* Make sure we are reading up to date values */+smp_mb();+ind=ind_counter&1;+snprintf(buf_prefix,sizeof(buf_prefix),+"isolation %s/%d/%d (cpu %d)",+desc->comm[ind],desc->tgid[ind],+desc->pid[ind],cpu);+desc->warned[ind]=true;+ind_counter_old=ind_counter;+/* Record the warned flag, then re-read descriptor */+smp_mb();+ind_counter=atomic_read(&desc->curr_index);+/*+*Ifthecounterchanged,somethingwasupdated,so+*repeateverythingtogetthecurrentdata+*/+}while(ind_counter!=ind_counter_old);+}++va_start(args,fmt);+vsnprintf(buf,sizeof(buf),fmt,args);+va_end(args);++switch(level){+caseLOGLEVEL_EMERG:+pr_emerg("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_ALERT:+pr_alert("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_CRIT:+pr_crit("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_ERR:+pr_err("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_WARNING:+pr_warn("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_NOTICE:+pr_notice("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_INFO:+pr_info("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_DEBUG:+pr_debug("%s: %s",buf_prefix,buf);+break;+default:+/* No message without a valid level */+return0;+}+return1;+}++/*+*Dumpstackifneedbe.Thiscanbehelpfulevenfromthefinalexit+*tousermodecodesincestacktracessometimescarryinformationabout+*whatputyouintothekernel,e.g.aninterruptnumberencodedin+*theinitialentrystackframethatisstillvisibleatexittime.+*/+staticvoiddebug_dump_stack(void)+{+if(task_isolation_debug)+dump_stack();+}++/*+*Settheflagswordbutdon'ttrytoactuallystarttaskisolationyet.+*Wewillstartitwhenenteringuserspaceintask_isolation_start().+*/+inttask_isolation_request(unsignedintflags)+{+structtask_struct*task=current;++/*+*Thetaskisolationflagsshouldalwaysbeclearedjustby+*virtueofhavingenteredthekernel.+*/+WARN_ON_ONCE(test_tsk_thread_flag(task,TIF_TASK_ISOLATION));+WARN_ON_ONCE(task->task_isolation_flags!=0);+WARN_ON_ONCE(task->task_isolation_state!=STATE_NORMAL);++task->task_isolation_flags=flags;+if(!(task->task_isolation_flags&PR_TASK_ISOLATION_ENABLE))+return0;++/* We are trying to enable task isolation. */+set_tsk_thread_flag(task,TIF_TASK_ISOLATION);++/*+*Shutdownthevmstatworkersowe'renotinterruptedlater.+*Wehavetotrytodothishere(withinterruptsenabled)since+*wearecancelingdelayedworkandwillcallflush_work()+*(whichenablesinterrupts)andpossiblyschedule().+*/+quiet_vmstat_sync();++/* We return 0 here but we may change that in task_isolation_start(). */+return0;+}++/*+*Performactionsthatshouldbedoneimmediatelyonexitfromisolation.+*/+staticvoidfast_task_isolation_cpu_cleanup(void*info)+{+atomic_dec(&per_cpu(isol_exit_counter,smp_processor_id()));+/* At this point breaking isolation from other CPUs is possible again */++/*+*Thistaskisnolongerisolated(andifbyanychancethis+*isthewrongtask,it'salreadynotisolated)+*/+current->task_isolation_flags=0;+clear_tsk_thread_flag(current,TIF_TASK_ISOLATION);++/* Run the rest of cleanup later */+set_tsk_thread_flag(current,TIF_NOTIFY_RESUME);++/* Copy flags with task isolation disabled */+this_cpu_write(tsk_thread_flags_cache,+READ_ONCE(task_thread_info(current)->flags));+}++/* Disable task isolation for the specified task. */+staticvoidstop_isolation(structtask_struct*p)+{+intcpu,this_cpu;+unsignedlongflags;++this_cpu=get_cpu();+cpu=task_cpu(p);+if(atomic_inc_return(&per_cpu(isol_exit_counter,cpu))>1){+/* Already exiting isolation */+atomic_dec(&per_cpu(isol_exit_counter,cpu));+put_cpu();+return;+}++if(p==current){+p->task_isolation_state=STATE_NORMAL;+fast_task_isolation_cpu_cleanup(NULL);+task_isolation_cpu_cleanup();+if(atomic_dec_return(&per_cpu(isol_counter,cpu))<0){+/* Is not isolated already */+atomic_inc(&per_cpu(isol_counter,cpu));+}+put_cpu();+}else{+if(atomic_dec_return(&per_cpu(isol_counter,cpu))<0){+/* Is not isolated already */+atomic_inc(&per_cpu(isol_counter,cpu));+atomic_dec(&per_cpu(isol_exit_counter,cpu));+put_cpu();+return;+}+/*+*Schedule"slow"cleanup.Thisrelieson+*TIF_NOTIFY_RESUMEbeingset+*/+spin_lock_irqsave(&task_isolation_cleanup_lock,flags);+cpumask_set_cpu(cpu,task_isolation_cleanup_map);+spin_unlock_irqrestore(&task_isolation_cleanup_lock,flags);+/*+*SettingflagsisdelegatedtotheCPUwhere+*isolatedtaskisrunning+*isol_exit_counterwillbedecrementedfromthereaswell.+*/+per_cpu(isol_break_csd,cpu).func=+fast_task_isolation_cpu_cleanup;+per_cpu(isol_break_csd,cpu).info=NULL;+per_cpu(isol_break_csd,cpu).flags=0;+smp_call_function_single_async(cpu,+&per_cpu(isol_break_csd,cpu));+put_cpu();+}+}++/*+*Thiscoderunswithinterruptsdisabledjustbeforethereturnto+*userspace,afteraprctl()hasrequestedenablingtaskisolation.+*Wetakewhateverstepsareneededtoavoidbeinginterruptedlater:+*drainthelrupages,stoptheschedulertick,etc.More+*functionalitymaybeaddedherelatertoavoidothertypesof+*interruptsfromotherkernelsubsystems.+*+*Ifwecan'tenabletaskisolation,weupdatethesyscallreturn+*valuewithanappropriateerror.+*/+voidtask_isolation_start(void)+{+interror;++/*+*WeshouldonlybecalledinSTATE_NORMAL(isolationdisabled),+*onourwayoutofthekernelfromtheprctl()thatturnediton.+*Ifweareexitingfromthekernelinanotherstate,itmeanswe+*madeitbackintothekernelwithoutdisablingtaskisolation,+*andweshouldinvestigatehow(andinanycasedisabletask+*isolationatthispoint).Weareclearlynotonthepathback+*fromtheprctl()sowedon'ttouchthesyscallreturnvalue.+*/+if(WARN_ON_ONCE(current->task_isolation_state!=STATE_NORMAL)){+/* Increment counter, this will allow isolation breaking */+if(atomic_inc_return(&per_cpu(isol_counter,+smp_processor_id()))>1){+atomic_dec(&per_cpu(isol_counter,smp_processor_id()));+}+atomic_inc(&per_cpu(isol_counter,smp_processor_id()));+stop_isolation(current);+return;+}++/*+*Mustbeaffinitizedtoasinglecorewithtaskisolationpossible.+*Inprinciplethiscouldberemotelymodifiedbetweentheprctl()+*andthereturntouserspace,sowehavetocheckithere.+*/+if(current->nr_cpus_allowed!=1||+!is_isolation_cpu(smp_processor_id())){+error=-EINVAL;+gotoerror;+}++/* If the vmstat delayed work is not canceled, we have to try again. */+if(!vmstat_idle()){+error=-EAGAIN;+gotoerror;+}++/* Try to stop the dynamic tick. */+error=try_stop_full_tick();+if(error)+gotoerror;++/* Drain the pagevecs to avoid unnecessary IPI flushes later. */+lru_add_drain();++/* Increment counter, this will allow isolation breaking */+if(atomic_inc_return(&per_cpu(isol_counter,+smp_processor_id()))>1){+atomic_dec(&per_cpu(isol_counter,smp_processor_id()));+}++/* Record isolated task IDs and name */+record_curr_isolated_task();++/* Copy flags with task isolation enabled */+this_cpu_write(tsk_thread_flags_cache,+READ_ONCE(task_thread_info(current)->flags));++current->task_isolation_state=STATE_ISOLATED;+return;++error:+/* Increment counter, this will allow isolation breaking */+if(atomic_inc_return(&per_cpu(isol_counter,+smp_processor_id()))>1){+atomic_dec(&per_cpu(isol_counter,smp_processor_id()));+}+stop_isolation(current);+syscall_set_return_value(current,current_pt_regs(),error,0);+}++/* Stop task isolation on the remote task and send it a signal. */+staticvoidsend_isolation_signal(structtask_struct*task)+{+intflags=task->task_isolation_flags;+kernel_siginfo_tinfo={+.si_signo=PR_TASK_ISOLATION_GET_SIG(flags)?:SIGKILL,+};++stop_isolation(task);+send_sig_info(info.si_signo,&info,task);+}++/* Only a few syscalls are valid once we are in task isolation mode. */+staticboolis_acceptable_syscall(intsyscall)+{+/* No need to incur an isolation signal if we are just exiting. */+if(syscall==__NR_exit||syscall==__NR_exit_group)+returntrue;++/* Check to see if it's the prctl for isolation. */+if(syscall==__NR_prctl){+unsignedlongarg[SYSCALL_MAX_ARGS];++syscall_get_arguments(current,current_pt_regs(),arg);+if(arg[0]==PR_TASK_ISOLATION)+returntrue;+}++returnfalse;+}++/*+*Thisroutineiscalledfromsyscallentry,preventsmostsyscalls+*fromexecuting,andifneededraisesasignaltonotifytheprocess.+*+*Notethatwehavetostopisolationbeforeweevenprintamessage+*here,sinceotherwisewemightendupreportinganinterruptdueto+*kickingtheprintkhandlingcode,ratherthanreportingthetrue+*causeofinterrupthere.+*+*Themessageisnotsuppressedbypreviousremotelytriggered+*messages.+*/+inttask_isolation_syscall(intsyscall)+{+structtask_struct*task=current;++if(is_acceptable_syscall(syscall)){+stop_isolation(task);+return0;+}++send_isolation_signal(task);++pr_task_isol_warn(smp_processor_id(),+"task_isolation lost due to syscall %d\n",+syscall);+debug_dump_stack();++syscall_set_return_value(task,current_pt_regs(),-ERESTARTNOINTR,-1);+return-1;+}++/*+*Thisroutineiscalledfromanyexceptionorirqthatdoesn't+*otherwisetriggerasignaltotheuserprocess(e.g.pagefault).+*+*Messageswillbesuppressedifthereisalreadyareportedremote+*causeforisolationbreaking,sowedon'tgeneratemultiple+*confusinglysimilarmessagesaboutthesameevent.+*/+void_task_isolation_interrupt(constchar*fmt,...)+{+structtask_struct*task=current;+va_listargs;+charbuf[100];++/* RCU should have been enabled prior to this point. */+RCU_LOCKDEP_WARN(!rcu_is_watching(),"kernel entry without RCU");++/* Are we exiting isolation already? */+if(atomic_read(&per_cpu(isol_exit_counter,smp_processor_id()))!=0){+task->task_isolation_state=STATE_NORMAL;+return;+}+/*+*Avoidreportinginterruptsthathappenafterwehaveprctl'ed+*toenableisolation,butbeforewehavereturnedtouserspace.+*/+if(task->task_isolation_state==STATE_NORMAL)+return;++va_start(args,fmt);+vsnprintf(buf,sizeof(buf),fmt,args);+va_end(args);++/* Handle NMIs minimally, since we can't send a signal. */+if(in_nmi()){+pr_task_isol_err(smp_processor_id(),+"isolation: in NMI; not delivering signal\n");+}else{+send_isolation_signal(task);+}++if(pr_task_isol_warn_supp(smp_processor_id(),+"task_isolation lost due to %s\n",buf))+debug_dump_stack();+}++/*+*Calledbeforewewakeupataskthathasasignaltoprocess.+*Needstobedonetohandleinterruptsthattriggersignals,which+*wedon'tcatchwithtask_isolation_interrupt()hooks.+*+*Thismessageisalsosuppressediftherewasalreadyaremotely+*causedmessageaboutthesameisolationbreakingevent.+*/+void_task_isolation_signal(structtask_struct*task)+{+structisol_task_desc*desc;+intind,cpu;+booldo_warn=(task->task_isolation_state==STATE_ISOLATED);++cpu=task_cpu(task);+desc=&per_cpu(isol_task_descs,cpu);+ind=atomic_read(&desc->curr_index)&1;+if(desc->warned[ind])+do_warn=false;++stop_isolation(task);++if(do_warn){+pr_warn("isolation: %s/%d/%d (cpu %d): task_isolation lost due to signal\n",+task->comm,task->tgid,task->pid,cpu);+debug_dump_stack();+}+}++/*+*Generateastackbacktraceifwearegoingtointerruptanothertask+*isolationprocess.+*/+voidtask_isolation_remote(intcpu,constchar*fmt,...)+{+structtask_struct*curr_task;+va_listargs;+charbuf[200];++if(!is_isolation_cpu(cpu)||!task_isolation_on_cpu(cpu))+return;++curr_task=current;++va_start(args,fmt);+vsnprintf(buf,sizeof(buf),fmt,args);+va_end(args);+if(pr_task_isol_warn(cpu,+"task_isolation lost due to %s by %s/%d/%d on cpu %d\n",+buf,+curr_task->comm,curr_task->tgid,+curr_task->pid,smp_processor_id()))+debug_dump_stack();+}++/*+*Generateastackbacktraceifanyofthecpusin"mask"arerunning+*taskisolationprocesses.+*/+voidtask_isolation_remote_cpumask(conststructcpumask*mask,+constchar*fmt,...)+{+structtask_struct*curr_task;+cpumask_var_twarn_mask;+va_listargs;+charbuf[200];+intcpu,first_cpu;++if(task_isolation_map==NULL||+!zalloc_cpumask_var(&warn_mask,GFP_KERNEL))+return;++first_cpu=-1;+for_each_cpu_and(cpu,mask,task_isolation_map){+if(task_isolation_on_cpu(cpu)){+if(first_cpu<0)+first_cpu=cpu;+else+cpumask_set_cpu(cpu,warn_mask);+}+}++if(first_cpu<0)+gotodone;++curr_task=current;++va_start(args,fmt);+vsnprintf(buf,sizeof(buf),fmt,args);+va_end(args);++if(cpumask_weight(warn_mask)==0)+pr_task_isol_warn(first_cpu,+"task_isolation lost due to %s by %s/%d/%d on cpu %d\n",+buf,curr_task->comm,curr_task->tgid,+curr_task->pid,smp_processor_id());+else+pr_task_isol_warn(first_cpu,+" and cpus %*pbl: task_isolation lost due to %s by %s/%d/%d on cpu %d\n",+cpumask_pr_args(warn_mask),+buf,curr_task->comm,curr_task->tgid,+curr_task->pid,smp_processor_id());+debug_dump_stack();++done:+free_cpumask_var(warn_mask);+}++/*+*CheckifgivenCPUisrunningisolatedtask.+*/+inttask_isolation_on_cpu(intcpu)+{+returntest_bit(TIF_TASK_ISOLATION,+&per_cpu(tsk_thread_flags_cache,cpu));+}++/*+*SetCPUscurrentlyrunningisolatedtasksinCPUmask.+*/+voidtask_isolation_cpumask(structcpumask*mask)+{+intcpu;++if(task_isolation_map==NULL)+return;++for_each_cpu(cpu,task_isolation_map)+if(task_isolation_on_cpu(cpu))+cpumask_set_cpu(cpu,mask);+}++/*+*ClearCPUscurrentlyrunningisolatedtasksinCPUmask.+*/+voidtask_isolation_clear_cpumask(structcpumask*mask)+{+intcpu;++if(task_isolation_map==NULL)+return;++for_each_cpu(cpu,task_isolation_map)+if(task_isolation_on_cpu(cpu))+cpumask_clear_cpu(cpu,mask);+}++/*+*Cleanupprocedure.Thecalltothisproceduremaybedelayed.+*/+voidtask_isolation_cpu_cleanup(void)+{+kick_hrtimer();+}++/*+*CheckifcleanupisscheduledonthecurrentCPU,andifso,runit.+*Intendedtobecalledfromnotify_resume()oranothersuchcallback+*onthetargetCPU.+*/+voidtask_isolation_check_run_cleanup(void)+{+intcpu;+unsignedlongflags;++spin_lock_irqsave(&task_isolation_cleanup_lock,flags);++cpu=smp_processor_id();++if(cpumask_test_cpu(cpu,task_isolation_cleanup_map)){+cpumask_clear_cpu(cpu,task_isolation_cleanup_map);+spin_unlock_irqrestore(&task_isolation_cleanup_lock,flags);+task_isolation_cpu_cleanup();+}else+spin_unlock_irqrestore(&task_isolation_cleanup_lock,flags);+}
@@ -882,6 +882,24 @@ static void tick_nohz_full_update_tick(struct tick_sched *ts)#endif}+#ifdef CONFIG_TASK_ISOLATION+inttry_stop_full_tick(void)+{+intcpu=smp_processor_id();+structtick_sched*ts=this_cpu_ptr(&tick_cpu_sched);++/* For an unstable clock, we should return a permanent error code. */+if(atomic_read(&tick_dep_mask)&TICK_DEP_MASK_CLOCK_UNSTABLE)+return-EINVAL;++if(!can_stop_full_tick(cpu,ts))+return-EAGAIN;++tick_nohz_stop_sched_tick(ts,cpu);+return0;+}+#endif+staticboolcan_stop_idle_tick(intcpu,structtick_sched*ts){/*
On Wed, Mar 04, 2020 at 04:07:12PM +0000, Alex Belits wrote:
The existing nohz_full mode is designed as a "soft" isolation mode
that makes tradeoffs to minimize userspace interruptions while
still attempting to avoid overheads in the kernel entry/exit path,
to provide 100% kernel semantics, etc.
However, some applications require a "hard" commitment from the
kernel to avoid interruptions, in particular userspace device driver
style applications, such as high-speed networking code.
This change introduces a framework to allow applications
to elect to have the "hard" semantics as needed, specifying
prctl(PR_TASK_ISOLATION, PR_TASK_ISOLATION_ENABLE) to do so.
The kernel must be built with the new TASK_ISOLATION Kconfig flag
to enable this mode, and the kernel booted with an appropriate
"isolcpus=nohz,domain,CPULIST" boot argument to enable
nohz_full and isolcpus. The "task_isolation" state is then indicated
by setting a new task struct field, task_isolation_flag, to the
value passed by prctl(), and also setting a TIF_TASK_ISOLATION
bit in the thread_info flags. When the kernel is returning to
userspace from the prctl() call and sees TIF_TASK_ISOLATION set,
it calls the new task_isolation_start() routine to arrange for
the task to avoid being interrupted in the future.
With interrupts disabled, task_isolation_start() ensures that kernel
subsystems that might cause a future interrupt are quiesced. If it
doesn't succeed, it adjusts the syscall return value to indicate that
fact, and userspace can retry as desired. In addition to stopping
the scheduler tick, the code takes any actions that might avoid
a future interrupt to the core, such as a worker thread being
scheduled that could be quiesced now (e.g. the vmstat worker)
or a future IPI to the core to clean up some state that could be
cleaned up now (e.g. the mm lru per-cpu cache).
Once the task has returned to userspace after issuing the prctl(),
if it enters the kernel again via system call, page fault, or any
other exception or irq, the kernel will kill it with SIGKILL.
In addition to sending a signal, the code supports a kernel
command-line "task_isolation_debug" flag which causes a stack
backtrace to be generated whenever a task loses isolation.
To allow the state to be entered and exited, the syscall checking
test ignores the prctl(PR_TASK_ISOLATION) syscall so that we can
clear the bit again later, and ignores exit/exit_group to allow
exiting the task without a pointless signal being delivered.
The prctl() API allows for specifying a signal number to use instead
of the default SIGKILL, to allow for catching the notification
signal; for example, in a production environment, it might be
helpful to log information to the application logging mechanism
before exiting. Or, the signal handler might choose to reset the
program counter back to the code segment intended to be run isolated
via prctl() to continue execution.
Hi Alew,
I'm glad this patchset is being resurected.
Reading that changelog, I like the general idea and the direction.
The diff is a bit scary though but I'll check the patches in detail
in the upcoming days.
In a number of cases we can tell on a remote cpu that we are
going to be interrupting the cpu, e.g. via an IPI or a TLB flush.
In that case we generate the diagnostic (and optional stack dump)
on the remote core to be able to deliver better diagnostics.
If the interrupt is not something caught by Linux (e.g. a
hypervisor interrupt) we can also request a reschedule IPI to
be sent to the remote core so it can be sure to generate a
signal to notify the process.
I'm wondering if it's wise to run that on a guest at all :-)
Or we should consider any guest exit to the host as a
disturbance, we would then need some sort of paravirt
driver to notify that, etc... That doesn't sound appealing.
Thanks.
From: Alex Belits <hidden> Date: 2020-03-08 05:33:18
On Thu, 2020-03-05 at 19:33 +0100, Frederic Weisbecker wrote:
On Wed, Mar 04, 2020 at 04:07:12PM +0000, Alex Belits wrote:
quoted
Hi Alew,
I'm glad this patchset is being resurected.
Reading that changelog, I like the general idea and the direction.
The diff is a bit scary though but I'll check the patches in detail
in the upcoming days.
I made some updates -- added missing code for arm and x86, restored
sign-off lines and updated commit messages.
This is the result of some work that mostly happened on earlier
versions and had to deal with the fact that timers and housekeeping
work often appeared on all CPUs, so some solutions may look like an
overkill. Nevertheless it was very helpful for finding the sources of
unexpected disturbances.
Also originally some of the race conditions and potential delayed work
at the time when a task is entering isolated state were considered
unavoidable. So the part in kernel was focused on correctness of
handling those conditions, while detection and dealing with their
consequences was done in userspace (in libtmc). Now it looks like there
may be much fewer such situations, however I am still not very thrilled
with the idea of complicating the kernel more than we have to.
Especially when it comes to code that is relevant only over few seconds
when the task is starting and entering isolated mode. So I have to
admit that some solutions look like "more EINTR than EINTR", and I
still like them more than making kernel side of entering/exiting
isolation even more complex than it is now.
I may be wrong, and there may be some more elegant solution, however I
don't see it now. Userspace-assisted isolation entering/exiting
procedure worked very well in a system with a huge number of cores,
threads, drivers with unusual features, etc., so at very least we have
some usable reference point.
quoted
In a number of cases we can tell on a remote cpu that we are
going to be interrupting the cpu, e.g. via an IPI or a TLB flush.
In that case we generate the diagnostic (and optional stack dump)
on the remote core to be able to deliver better diagnostics.
If the interrupt is not something caught by Linux (e.g. a
hypervisor interrupt) we can also request a reschedule IPI to
be sent to the remote core so it can be sure to generate a
signal to notify the process.
I'm wondering if it's wise to run that on a guest at all :-)
Or we should consider any guest exit to the host as a
disturbance, we would then need some sort of paravirt
driver to notify that, etc... That doesn't sound appealing.
Why not? I am not a big fan of virtualization, however people seem to
use it for all kinds of purposes now, and we only have to propagate (or
reject) isolation requests from guest to host (as long as resource and
permissions policy allow that). For KVM it would be literally
replicating guest task isolation state on the host, and as long as CPU
core is isolated, does it really matter if the task was created with
two layers of virtualization instead of one?
For isolation to make sense, it's still code running on a CPU with
fixed address mapping. If this is still the case, virtualization only
determines what can be in that space, not how it behaves. If this is
not the case, and task causes kernel code to run, be it guest or host
kernel, then something is wrong, and isolation is broken. Not very
different from behavior without virtualization.
This would be very bad for early days of virtualization when very
little could be done by a guest without host messing with it. Now, when
pieces of hardware can be (relatively) safely given to the guest
userspace to work on, we can just as well let it run isolated.
I like the idea as well, especially the reporting infrastructure, and
would like to see something like this integrated upstream.
On Thu, Mar 05, 2020 at 07:33:13PM +0100, Frederic Weisbecker wrote:
On Wed, Mar 04, 2020 at 04:07:12PM +0000, Alex Belits wrote:
quoted
The existing nohz_full mode is designed as a "soft" isolation mode
that makes tradeoffs to minimize userspace interruptions while
still attempting to avoid overheads in the kernel entry/exit path,
to provide 100% kernel semantics, etc.
However, some applications require a "hard" commitment from the
kernel to avoid interruptions, in particular userspace device driver
style applications, such as high-speed networking code.
This change introduces a framework to allow applications
to elect to have the "hard" semantics as needed, specifying
prctl(PR_TASK_ISOLATION, PR_TASK_ISOLATION_ENABLE) to do so.
The kernel must be built with the new TASK_ISOLATION Kconfig flag
to enable this mode, and the kernel booted with an appropriate
"isolcpus=nohz,domain,CPULIST" boot argument to enable
nohz_full and isolcpus. The "task_isolation" state is then indicated
by setting a new task struct field, task_isolation_flag, to the
value passed by prctl(), and also setting a TIF_TASK_ISOLATION
bit in the thread_info flags. When the kernel is returning to
userspace from the prctl() call and sees TIF_TASK_ISOLATION set,
it calls the new task_isolation_start() routine to arrange for
the task to avoid being interrupted in the future.
With interrupts disabled, task_isolation_start() ensures that kernel
subsystems that might cause a future interrupt are quiesced. If it
doesn't succeed, it adjusts the syscall return value to indicate that
fact, and userspace can retry as desired. In addition to stopping
the scheduler tick, the code takes any actions that might avoid
a future interrupt to the core, such as a worker thread being
scheduled that could be quiesced now (e.g. the vmstat worker)
or a future IPI to the core to clean up some state that could be
cleaned up now (e.g. the mm lru per-cpu cache).
Once the task has returned to userspace after issuing the prctl(),
if it enters the kernel again via system call, page fault, or any
other exception or irq, the kernel will kill it with SIGKILL.
This severely limits usage of the interface.
I suppose the reason for blocking system calls is to make sure
userspace does not initiate actions that might generate interruptions,
such as IPI flushes (memory unmaps or changes), vmstat work items
(page dirtying), or is there any reason for it ?
+/* Only a few syscalls are valid once we are in task isolation mode. */
+static bool is_acceptable_syscall(int syscall)
+{
+ /* No need to incur an isolation signal if we are just exiting. */
+ if (syscall == __NR_exit || syscall == __NR_exit_group)
+ return true;
+
+ /* Check to see if it's the prctl for isolation. */
+ if (syscall == __NR_prctl) {
+ unsigned long arg[SYSCALL_MAX_ARGS];
+
+ syscall_get_arguments(current, current_pt_regs(), arg);
+ if (arg[0] == PR_TASK_ISOLATION)
+ return true;
+ }
+
+ return false;
+}
quoted
In addition to sending a signal, the code supports a kernel
command-line "task_isolation_debug" flag which causes a stack
backtrace to be generated whenever a task loses isolation.
To allow the state to be entered and exited, the syscall checking
test ignores the prctl(PR_TASK_ISOLATION) syscall so that we can
clear the bit again later, and ignores exit/exit_group to allow
exiting the task without a pointless signal being delivered.
The prctl() API allows for specifying a signal number to use instead
of the default SIGKILL, to allow for catching the notification
signal; for example, in a production environment, it might be
helpful to log information to the application logging mechanism
before exiting. Or, the signal handler might choose to reset the
program counter back to the code segment intended to be run isolated
via prctl() to continue execution.
Hi Alew,
I'm glad this patchset is being resurected.
Reading that changelog, I like the general idea and the direction.
The diff is a bit scary though but I'll check the patches in detail
in the upcoming days.
quoted
In a number of cases we can tell on a remote cpu that we are
going to be interrupting the cpu, e.g. via an IPI or a TLB flush.
In that case we generate the diagnostic (and optional stack dump)
on the remote core to be able to deliver better diagnostics.
If the interrupt is not something caught by Linux (e.g. a
hypervisor interrupt) we can also request a reschedule IPI to
be sent to the remote core so it can be sure to generate a
signal to notify the process.
I'm wondering if it's wise to run that on a guest at all :-)
Or we should consider any guest exit to the host as a
disturbance, we would then need some sort of paravirt
driver to notify that, etc... That doesn't sound appealing.
Thanks.
On Wed, Mar 04, 2020 at 04:07:12PM +0000, Alex Belits wrote:
+
+/*
+ * Print message prefixed with the description of the current (or
+ * last) isolated task on a given CPU. Intended for isolation breaking
+ * messages that include target task for the user's convenience.
+ *
+ * Messages produced with this function may have obsolete task
+ * information if isolated tasks managed to exit, start and enter
+ * isolation multiple times, or multiple tasks tried to enter
+ * isolation on the same CPU at once. For those unusual cases it would
+ * contain a valid description of the cause for isolation breaking and
+ * target CPU number, just not the correct description of which task
+ * ended up losing isolation.
+ */
+int task_isolation_message(int cpu, int level, bool supp, const char *fmt, ...)
+{
+ struct isol_task_desc *desc;
+ struct task_struct *task;
+ va_list args;
+ char buf_prefix[TASK_COMM_LEN + 20 + 3 * 20];
+ char buf[200];
+ int curr_cpu, ind_counter, ind_counter_old, ind;
+
+ curr_cpu = get_cpu();
+ desc = &per_cpu(isol_task_descs, cpu);
+ ind_counter = atomic_read(&desc->curr_index);
+
+ if (curr_cpu == cpu) {
+ /*
+ * Message is for the current CPU so current
+ * task_struct should be used instead of cached
+ * information.
+ *
+ * Like in other diagnostic messages, if issued from
+ * interrupt context, current will be the interrupted
+ * task. Unlike other diagnostic messages, this is
+ * always relevant because the message is about
+ * interrupting a task.
+ */
+ ind = ind_counter & 1;
+ if (supp && desc->warned[ind]) {
+ /*
+ * If supp is true, skip the message if the
+ * same task was mentioned in the message
+ * originated on remote CPU, and it did not
+ * re-enter isolated state since then (warned
+ * is true). Only local messages following
+ * remote messages, likely about the same
+ * isolation breaking event, are skipped to
+ * avoid duplication. If remote cause is
+ * immediately followed by a local one before
+ * isolation is broken, local cause is skipped
+ * from messages.
+ */
+ put_cpu();
+ return 0;
+ }
+ task = current;
+ snprintf(buf_prefix, sizeof(buf_prefix),
+ "isolation %s/%d/%d (cpu %d)",
+ task->comm, task->tgid, task->pid, cpu);
+ put_cpu();
+ } else {
+ /*
+ * Message is for remote CPU, use cached information.
+ */
+ put_cpu();
+ /*
+ * Make sure, index remained unchanged while data was
+ * copied. If it changed, data that was copied may be
+ * inconsistent because two updates in a sequence could
+ * overwrite the data while it was being read.
+ */
+ do {
+ /* Make sure we are reading up to date values */
+ smp_mb();
+ ind = ind_counter & 1;
+ snprintf(buf_prefix, sizeof(buf_prefix),
+ "isolation %s/%d/%d (cpu %d)",
+ desc->comm[ind], desc->tgid[ind],
+ desc->pid[ind], cpu);
+ desc->warned[ind] = true;
+ ind_counter_old = ind_counter;
+ /* Record the warned flag, then re-read descriptor */
+ smp_mb();
+ ind_counter = atomic_read(&desc->curr_index);
+ /*
+ * If the counter changed, something was updated, so
+ * repeat everything to get the current data
+ */
+ } while (ind_counter != ind_counter_old);
+ }
So the need to log the fact we are sending an event to a remote CPU that *may be*
running an isolated task makes things very complicated and even racy.
How bad would it be to only log those interruptions once they land on the target?
Thanks.
From: Alex Belits <hidden> Date: 2020-03-08 06:07:17
On Fri, 2020-03-06 at 16:26 +0100, Frederic Weisbecker wrote:
On Wed, Mar 04, 2020 at 04:07:12PM +0000, Alex Belits wrote:
quoted
do {
+ /* Make sure we are reading up to date values
*/
+ smp_mb();
+ ind = ind_counter & 1;
+ snprintf(buf_prefix, sizeof(buf_prefix),
+ "isolation %s/%d/%d (cpu %d)",
+ desc->comm[ind], desc->tgid[ind],
+ desc->pid[ind], cpu);
+ desc->warned[ind] = true;
+ ind_counter_old = ind_counter;
+ /* Record the warned flag, then re-read
descriptor */
+ smp_mb();
+ ind_counter = atomic_read(&desc->curr_index);
+ /*
+ * If the counter changed, something was
updated, so
+ * repeat everything to get the current data
+ */
+ } while (ind_counter != ind_counter_old);
+ }
So the need to log the fact we are sending an event to a remote CPU
that *may be*
running an isolated task makes things very complicated and even racy.
The only reason why the result of this would be wrong, is the race
between multiple causes of breaking isolation of the same task or race
with the task exiting isolation on its own at the same time (and
possibly re-entering it, or even another task entering on the same CPU
core). This is possible, however for all practical purposes we are
still logging an isolation-breaking event that happened while a real
isolated task was running. We should keep in mind the possibility that
this isolation-breaking event could be preempted by another isolation
breaking cause, and all of them will be recorded even if only one ended
up causing fast_task_isolation_cpu_cleanup() to be called on the target
CPU core.
How bad would it be to only log those interruptions once they land on
the target?
For the purpose of determining the cause of isolation breaking -- very
bad. Early versions of this made people tear their hair out trying to
divine, where some IPI came from. Then there was a monstrosity that did
some rather unsafe manipulations with task_struct, however it was only
suitable as a temporary mechanism for development. This version keeps
things consistent and only shows up when there is something that should
be reported.
On Wed, Mar 04, 2020 at 04:07:12PM +0000, Alex Belits wrote:
+#ifdef CONFIG_TASK_ISOLATION
+int try_stop_full_tick(void)
+{
+ int cpu = smp_processor_id();
+ struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched);
+
+ /* For an unstable clock, we should return a permanent error code. */
+ if (atomic_read(&tick_dep_mask) & TICK_DEP_MASK_CLOCK_UNSTABLE)
+ return -EINVAL;
+
+ if (!can_stop_full_tick(cpu, ts))
+ return -EAGAIN;
Note that the stop_tick naming in nohz can be misleading. It means
we actually leave the periodic mode and we enter in dynamic tick mode.
In practice it means that the tick is delayed until the next event, which
in the worst case may well be in 1 ms and in the best case never. So what
you probably want to check instead is whether the tick has been entirely
stopped (ie: we called hrtimer_cancel(&ts->sched_timer)).
Thanks.
From: Alex Belits <hidden> Date: 2020-03-08 07:16:54
On Fri, 2020-03-06 at 17:00 +0100, Frederic Weisbecker wrote:
On Wed, Mar 04, 2020 at 04:07:12PM +0000, Alex Belits wrote:
quoted
+#ifdef CONFIG_TASK_ISOLATION
+int try_stop_full_tick(void)
+{
+ int cpu = smp_processor_id();
+ struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched);
+
+ /* For an unstable clock, we should return a permanent error
code. */
+ if (atomic_read(&tick_dep_mask) & TICK_DEP_MASK_CLOCK_UNSTABLE)
+ return -EINVAL;
+
+ if (!can_stop_full_tick(cpu, ts))
+ return -EAGAIN;
Note that the stop_tick naming in nohz can be misleading. It means
we actually leave the periodic mode and we enter in dynamic tick
mode.
In practice it means that the tick is delayed until the next event,
which
in the worst case may well be in 1 ms and in the best case never. So
what
you probably want to check instead is whether the tick has been
entirely
stopped (ie: we called hrtimer_cancel(&ts->sched_timer)).
This is a part of solution where libtmc in userspace checks for timers
from another core before it confirms that the core that is entering
isolation can continue. Since, indeed, it is possible that some events
are pending, it is up to userspace to tell the task that it's not
really isolated yet, should exit and re-enter isolation when everything
is done. Or that it will be too much of the wait, and it should be seen
as an error, reported, etc.
Maybe it would be better if we checked for timer state and returned
-EAGAIN when it is running at this point, and left userspace check for
those cases when this did not work due to some race and preemption.
However I still want to assume that as long as there is no complete
prohibition of scheduling things on isolated CPUs, there might be
things that will enable this timer at unexpected times while we are
returning to userspace or even immediately after we got into userspace.
From: Alex Belits <hidden> Date: 2020-03-04 16:08:43
From: Chris Metcalf <redacted>
This commit adds task isolation hooks as follows:
- __handle_domain_irq() generates an isolation warning for the
local task
- irq_work_queue_on() generates an isolation warning for the remote
task being interrupted for irq_work
- generic_exec_single() generates a remote isolation warning for
the remote cpu being IPI'd
- smp_call_function_many() generates a remote isolation warning for
the set of remote cpus being IPI'd
Calls to task_isolation_remote() or task_isolation_interrupt() can
be placed in the platform-independent code like this when doing so
results in fewer lines of code changes, as for example is true of
the users of the arch_send_call_function_*() APIs. Or, they can be
placed in the per-architecture code when there are many callers,
as for example is true of the smp_send_reschedule() call.
A further cleanup might be to create an intermediate layer, so that
for example smp_send_reschedule() is a single generic function that
just calls arch_smp_send_reschedule(), allowing generic code to be
called every time smp_send_reschedule() is invoked. But for now, we
just update either callers or callees as makes most sense.
Signed-off-by: Alex Belits <redacted>
---
kernel/irq/irqdesc.c | 9 +++++++++
kernel/irq_work.c | 5 ++++-
kernel/smp.c | 6 +++++-
3 files changed, 18 insertions(+), 2 deletions(-)
@@ -466,6 +469,7 @@ static void smp_call_function_many_cond(const struct cpumask *mask,}/* Send a message to all CPUs in the map */+task_isolation_remote_cpumask(cfd->cpumask_ipi,"IPI function");arch_send_call_function_ipi_mask(cfd->cpumask_ipi);if(wait){
From: Alex Belits <hidden> Date: 2020-03-04 16:10:13
From: Chris Metcalf <redacted>
In prepare_exit_to_usermode(), call task_isolation_start() for
TIF_TASK_ISOLATION tasks.
In syscall_trace_enter_phase1(), add the necessary support for
reporting syscalls for task-isolation processes.
Add task_isolation_remote() calls for the kernel exception types
that do not result in signals, namely non-signalling page faults.
Signed-off-by: Alex Belits <redacted>
---
arch/x86/Kconfig | 1 +
arch/x86/entry/common.c | 13 +++++++++++++
arch/x86/include/asm/apic.h | 3 +++
arch/x86/include/asm/thread_info.h | 4 +++-
arch/x86/kernel/apic/ipi.c | 2 ++
arch/x86/mm/fault.c | 4 ++++
6 files changed, 26 insertions(+), 1 deletion(-)
@@ -92,6 +92,7 @@ struct thread_info {#define TIF_NOCPUID 15 /* CPUID is not accessible in userland */#define TIF_NOTSC 16 /* TSC is not accessible in userland */#define TIF_IA32 17 /* IA32 compatibility process */+#define TIF_TASK_ISOLATION 18 /* task isolation enabled for task */#define TIF_NOHZ 19 /* in adaptive nohz mode */#define TIF_MEMDIE 20 /* is terminating due to OOM killer */#define TIF_POLLING_NRFLAG 21 /* idle is polling for TIF_NEED_RESCHED */
@@ -1483,6 +1484,9 @@ void do_user_addr_fault(struct pt_regs *regs,perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MIN,1,regs,address);}+/* No signal was generated, but notify task-isolation tasks. */+task_isolation_interrupt("page fault at %#lx",address);+check_v8086_mode(regs,address,tsk);}NOKPROBE_SYMBOL(do_user_addr_fault);
From: Alex Belits <hidden> Date: 2020-03-04 16:11:08
From: Chris Metcalf <redacted>
In do_notify_resume(), call task_isolation_start() for
TIF_TASK_ISOLATION tasks. Add _TIF_TASK_ISOLATION to _TIF_WORK_MASK,
and define a local NOTIFY_RESUME_LOOP_FLAGS to check in the loop,
since we don't clear _TIF_TASK_ISOLATION in the loop.
We tweak syscall_trace_enter() slightly to carry the "flags"
value from current_thread_info()->flags for each of the tests,
rather than doing a volatile read from memory for each one. This
avoids a small overhead for each test, and in particular avoids
that overhead for TIF_NOHZ when TASK_ISOLATION is not enabled.
We instrument the smp_send_reschedule() routine so that it checks for
isolated tasks and generates a suitable warning if needed.
Finally, report on page faults in task-isolation processes in
do_page_faults().
Signed-off-by: Alex Belits <redacted>
---
arch/arm64/Kconfig | 1 +
arch/arm64/include/asm/thread_info.h | 5 ++++-
arch/arm64/kernel/ptrace.c | 10 ++++++++++
arch/arm64/kernel/signal.c | 13 ++++++++++++-
arch/arm64/kernel/smp.c | 7 +++++++
arch/arm64/mm/fault.c | 5 +++++
6 files changed, 39 insertions(+), 2 deletions(-)
@@ -1836,6 +1837,15 @@ int syscall_trace_enter(struct pt_regs *regs)return-1;}+/*+*Intaskisolationmode,wemaypreventthesyscallfrom+*running,andifsowealsodeliverasignaltotheprocess.+*/+if(test_thread_flag(TIF_TASK_ISOLATION)){+if(task_isolation_syscall(regs->syscallno)==-1)+return-1;+}+/* Do the secure computing after ptrace; failures should be fast. */if(secure_computing()==-1)return-1;
@@ -543,6 +544,10 @@ static int __kprobes do_page_fault(unsigned long addr, unsigned int esr,*/if(likely(!(fault&(VM_FAULT_ERROR|VM_FAULT_BADMAP|VM_FAULT_BADACCESS)))){+/* No signal was generated, but notify task-isolation tasks. */+if(user_mode(regs))+task_isolation_interrupt("page fault at %#lx",addr);+/**Major/minorpagefaultaccountingisonlydone*once.Ifwegothrougharetry,itisextremely
From: Mark Rutland <mark.rutland@arm.com> Date: 2020-03-04 16:31:09
Hi Alex,
For patches affecting arm64, please CC LAKML and the arm64 maintainers
(Will and Catalin). I've Cc'd the maintainers here.
On Wed, Mar 04, 2020 at 04:10:28PM +0000, Alex Belits wrote:
From: Chris Metcalf <redacted>
In do_notify_resume(), call task_isolation_start() for
TIF_TASK_ISOLATION tasks. Add _TIF_TASK_ISOLATION to _TIF_WORK_MASK,
and define a local NOTIFY_RESUME_LOOP_FLAGS to check in the loop,
since we don't clear _TIF_TASK_ISOLATION in the loop.
We tweak syscall_trace_enter() slightly to carry the "flags"
value from current_thread_info()->flags for each of the tests,
rather than doing a volatile read from memory for each one. This
avoids a small overhead for each test, and in particular avoids
that overhead for TIF_NOHZ when TASK_ISOLATION is not enabled.
Stale commit message?
Looking at the patch below, this doesn't seem to be the case; it just
calls test_thread_flag(TIF_TASK_ISOLATION).
We instrument the smp_send_reschedule() routine so that it checks for
isolated tasks and generates a suitable warning if needed.
Finally, report on page faults in task-isolation processes in
do_page_faults().
Signed-off-by: Alex Belits <redacted>
The From line says this was from Chris Metcalf, but he's missing from
the Sign-off chain here, which isn't right.
@@ -1836,6 +1837,15 @@ int syscall_trace_enter(struct pt_regs *regs)return-1;}+/*+*Intaskisolationmode,wemaypreventthesyscallfrom+*running,andifsowealsodeliverasignaltotheprocess.+*/+if(test_thread_flag(TIF_TASK_ISOLATION)){+if(task_isolation_syscall(regs->syscallno)==-1)+return-1;+}
As above, this doesn't match the commit message.
AFAICT, task_isolation_syscall() always returns either 0 or -1, which
isn't great as an API. I see secure_computing() seems to do the same,
and it'd be nice to clean that up to either be a real erro code or a
boolean.
quoted hunk
+
/* Do the secure computing after ptrace; failures should be fast. */
if (secure_computing() == -1)
return -1;
@@ -543,6 +544,10 @@ static int __kprobes do_page_fault(unsigned long addr, unsigned int esr,*/if(likely(!(fault&(VM_FAULT_ERROR|VM_FAULT_BADMAP|VM_FAULT_BADACCESS)))){+/* No signal was generated, but notify task-isolation tasks. */+if(user_mode(regs))+task_isolation_interrupt("page fault at %#lx",addr);+
We check user_mode(regs) much earlier in this function to set
FAULT_FLAG_USER. Is there some reason this cannot live there?
Also, this seems to be a tracing hook -- is this necessary?
Thanks,
Mark.
/*
* Major/minor page fault accounting is only done
* once. If we go through a retry, it is extremely
--
2.20.1
From: Alex Belits <hidden> Date: 2020-03-08 04:49:00
On Wed, 2020-03-04 at 16:31 +0000, Mark Rutland wrote:
Hi Alex,
For patches affecting arm64, please CC LAKML and the arm64
maintainers
(Will and Catalin). I've Cc'd the maintainers here.
Thanks. Added them to Cc:.
On Wed, Mar 04, 2020 at 04:10:28PM +0000, Alex Belits wrote:
quoted
From: Chris Metcalf <redacted>
In do_notify_resume(), call task_isolation_start() for
TIF_TASK_ISOLATION tasks. Add _TIF_TASK_ISOLATION to
_TIF_WORK_MASK,
and define a local NOTIFY_RESUME_LOOP_FLAGS to check in the loop,
since we don't clear _TIF_TASK_ISOLATION in the loop.
We tweak syscall_trace_enter() slightly to carry the "flags"
value from current_thread_info()->flags for each of the tests,
rather than doing a volatile read from memory for each one. This
avoids a small overhead for each test, and in particular avoids
that overhead for TIF_NOHZ when TASK_ISOLATION is not enabled.
Stale commit message?
Looking at the patch below, this doesn't seem to be the case; it just
calls test_thread_flag(TIF_TASK_ISOLATION).
Right. I had to revert to a simple check to match the current
implementation of this function.
We instrument the smp_send_reschedule() routine so that
itchecks for
quoted
isolated tasks and generates a suitable warning if needed.
Finally, report on page faults in task-isolation processes in
do_page_faults().
Signed-off-by: Alex Belits <redacted>
The From line says this was from Chris Metcalf, but he's missing from
the Sign-off chain here, which isn't right.
I have posted updated patches with properly preserved sign-off lines
and descriptions of updates.
@@ -1836,6 +1837,15 @@ int syscall_trace_enter(struct pt_regs
*regs)
return -1;
}
+ /*
+ * In task isolation mode, we may prevent the syscall from
+ * running, and if so we also deliver a signal to the process.
+ */
+ if (test_thread_flag(TIF_TASK_ISOLATION)) {
+ if (task_isolation_syscall(regs->syscallno) == -1)
+ return -1;
+ }
As above, this doesn't match the commit message.
AFAICT, task_isolation_syscall() always returns either 0 or -1, which
isn't great as an API. I see secure_computing() seems to do the same,
and it'd be nice to clean that up to either be a real erro code or a
boolean.
Boolean may make more sense considering that we are not dealing with
errors returned by some operation the fact that calls were made from
the wrong state.
quoted
quoted
quoted
+
/* Do the secure computing after ptrace; failures should be
fast. */
if (secure_computing() == -1)
return -1;
diff --git a/arch/arm64/kernel/signal.c
b/arch/arm64/kernel/signal.c
index 339882db5a91..d488c91a4877 100644
Are these tracing hooks?
Surely they aren't necessary for functional correctness?
This is necessary to properly break isolation and send a signal when
something disturbed the isolated task, and to tell the user (likely a
developer), why this happened. There are some legitimate reasons for
breaking isolation (for example, a signal that terminates the process)
and very large number of possible things that should not happen to an
isolated task -- page fault caused by access to unmapped addresses,
syscall issued by the task in isolated state, interrupt directed to the
core running isolated task, timer remaining there, or kernel trying to
run something on that core. It was very helpful to know what exactly
happened, and if not this reporting, I would have hard time debugging
the drivers and subsystems that called their functions and scheduled
their threads all over the place. So both reliable breaking of
isolation and reporting the cause is important.
quoted
switch (ipinr) {
case IPI_RESCHEDULE:
scheduler_ipi();
@@ -543,6 +544,10 @@ static int __kprobes do_page_fault(unsigned
long addr, unsigned int esr,
*/
if (likely(!(fault & (VM_FAULT_ERROR | VM_FAULT_BADMAP |
VM_FAULT_BADACCESS)))) {
+ /* No signal was generated, but notify task-isolation
tasks. */
+ if (user_mode(regs))
+ task_isolation_interrupt("page fault at %#lx",
addr);
+
We check user_mode(regs) much earlier in this function to set
FAULT_FLAG_USER. Is there some reason this cannot live there?
Also, this seems to be a tracing hook -- is this necessary?
This is another point where task isolation state is broken, task
receives a signal and the reason is logged. If not isolation, task
would not know that anything happened, but it is important for
isolation.
In general if isolated task is supposed to be running on a CPU core, it
has to stay in userspace. So if for some reason a kernel code is
running on that CPU core, this is either a task leaving isolation on
its own or isolation is broken and the task should be notified about
it. The only problem is how to properly and reliably explain why this
happened.
Thanks!
--
Alex
From: Alex Belits <hidden> Date: 2020-03-04 16:12:15
From: Francis Giraldeau <redacted>
This patch is a port of the task isolation functionality to the arm 32-bit
architecture. The task isolation needs an additional thread flag that
requires to change the entry assembly code to accept a bitfield larger than
one byte. The constants _TIF_SYSCALL_WORK and _TIF_WORK_MASK are now
defined in the literal pool. The rest of the patch is straightforward and
reflects what is done on other architectures.
To avoid problems with the tst instruction in the v7m build, we renumber
TIF_SECCOMP to bit 8 and let TIF_TASK_ISOLATION use bit 7.
Signed-off-by: Alex Belits <redacted>
---
arch/arm/Kconfig | 1 +
arch/arm/include/asm/thread_info.h | 10 +++++++---
arch/arm/kernel/entry-common.S | 15 ++++++++++-----
arch/arm/kernel/signal.c | 10 +++++++++-
arch/arm/kernel/smp.c | 4 ++++
arch/arm/mm/fault.c | 8 +++++++-
6 files changed, 38 insertions(+), 10 deletions(-)
@@ -251,7 +255,8 @@ local_restart:ldrr10,[tsk,#TI_FLAGS] @ check for syscall tracingstmdbsp!,{r4,r5}@pushfifthandsixthargs-tstr10,#_TIF_SYSCALL_WORK @ are we tracing syscalls?+ldrr11,=_TIF_SYSCALL_WORK@arewetracingsyscalls?+tstr10,r11bne__sys_traceinvoke_syscalltbl,scno,r10,__ret_fast_syscall
@@ -332,8 +333,13 @@ do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)/**Handlethe"normal"casefirst-VM_FAULT_MAJOR*/-if(likely(!(fault&(VM_FAULT_ERROR|VM_FAULT_BADMAP|VM_FAULT_BADACCESS))))+if(likely(!(fault&(VM_FAULT_ERROR|VM_FAULT_BADMAP|+VM_FAULT_BADACCESS)))){+/* No signal was generated, but notify task-isolation tasks. */+if(user_mode(regs))+task_isolation_interrupt("page fault at %#lx",addr);return0;+}/**Ifweareinkernelmodeatthispoint,we
From: Alex Belits <hidden> Date: 2020-03-04 16:13:06
From: Yuri Norov <redacted>
For nohz_full CPUs the desirable behavior is to receive interrupts
generated by tick_nohz_full_kick_cpu(). But for hard isolation it's
obviously not desirable because it breaks isolation.
This patch adds check for it.
Signed-off-by: Alex Belits <redacted>
---
kernel/time/tick-sched.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
On Wed, Mar 04, 2020 at 04:12:40PM +0000, Alex Belits wrote:
quoted hunk
From: Yuri Norov <redacted>
For nohz_full CPUs the desirable behavior is to receive interrupts
generated by tick_nohz_full_kick_cpu(). But for hard isolation it's
obviously not desirable because it breaks isolation.
This patch adds check for it.
Signed-off-by: Alex Belits <redacted>
---
kernel/time/tick-sched.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
I fear you can't do that. A nohz full CPU is kicked for a reason.
As for the other cases, you need to fix the callers.
In the general case, randomly ignoring an interrupt is a correctness
issue.
Thanks.
From: Alex Belits <hidden> Date: 2020-03-08 07:29:12
On Fri, 2020-03-06 at 17:03 +0100, Frederic Weisbecker wrote:
On Wed, Mar 04, 2020 at 04:12:40PM +0000, Alex Belits wrote:
quoted
From: Yuri Norov <redacted>
For nohz_full CPUs the desirable behavior is to receive interrupts
generated by tick_nohz_full_kick_cpu(). But for hard isolation it's
obviously not desirable because it breaks isolation.
This patch adds check for it.
Signed-off-by: Alex Belits <redacted>
---
kernel/time/tick-sched.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
I fear you can't do that. A nohz full CPU is kicked for a reason.
As for the other cases, you need to fix the callers.
In the general case, randomly ignoring an interrupt is a correctness
issue.
Not ignoring, just delaying until we are back from userspace. We know
that everything was done on this CPU when we successfully entered
userspace in isolated mode -- otherwise we would be kicked out. We
restart timers when we are back in kernel again on cleanup, so things
will be back to normal at that point. Between those moments we can just
as well remain in userspace and forget about the timers until we are
back in kernel.
On Sun, Mar 08, 2020 at 07:28:22AM +0000, Alex Belits wrote:
On Fri, 2020-03-06 at 17:03 +0100, Frederic Weisbecker wrote:
quoted
On Wed, Mar 04, 2020 at 04:12:40PM +0000, Alex Belits wrote:
quoted
From: Yuri Norov <redacted>
For nohz_full CPUs the desirable behavior is to receive interrupts
generated by tick_nohz_full_kick_cpu(). But for hard isolation it's
obviously not desirable because it breaks isolation.
This patch adds check for it.
Signed-off-by: Alex Belits <redacted>
---
kernel/time/tick-sched.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
I fear you can't do that. A nohz full CPU is kicked for a reason.
As for the other cases, you need to fix the callers.
In the general case, randomly ignoring an interrupt is a correctness
issue.
Not ignoring, just delaying until we are back from userspace. We know
that everything was done on this CPU when we successfully entered
userspace in isolated mode -- otherwise we would be kicked out. We
restart timers when we are back in kernel again on cleanup, so things
will be back to normal at that point. Between those moments we can just
as well remain in userspace and forget about the timers until we are
back in kernel.
Well, if another CPU requests the tick on our isolated CPU, we can't ignore
it. This can be a posix cpu timer belonging to our process, a timer bound
to our CPU or tasks added to our CPU that require the scheduler tick.
Denying any of that can crash the kernel randomly.
The only thing we can do is to simply avoid these situations. But those
are requirements anyway if you want to run a task undisturbed.
From: Alex Belits <hidden> Date: 2020-03-04 16:14:00
From: Yuri Norov <redacted>
If CPU runs isolated task, there's no any backlog on it, and
so we don't need to flush it. Currently flush_all_backlogs()
enqueues corresponding work on all CPUs including ones that run
isolated tasks. It leads to breaking task isolation for nothing.
In this patch, backlog flushing is enqueued only on non-isolated CPUs.
Signed-off-by: Alex Belits <redacted>
---
net/core/dev.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
From: Alex Belits <hidden> Date: 2020-03-04 16:14:56
From: Yuri Norov <redacted>
CPUs running isolated tasks are in userspace, so they don't have to
perform ring buffer updates immediately. If ring_buffer_resize()
schedules the update on those CPUs, isolation is broken. To prevent
that, updates for CPUs running isolated tasks are performed locally,
like for offline CPUs.
A race condition between this update and isolation breaking is avoided
at the cost of disabling per_cpu buffer writing for the time of update
when it coincides with isolation breaking.
Signed-off-by: Alex Belits <redacted>
---
kernel/trace/ring_buffer.c | 61 ++++++++++++++++++++++++++++++++++----
1 file changed, 55 insertions(+), 6 deletions(-)
@@ -1701,6 +1702,37 @@ static void update_pages_handler(struct work_struct *work)complete(&cpu_buffer->update_done);}+staticboolupdate_if_isolated(structring_buffer_per_cpu*cpu_buffer,+intcpu)+{+boolrv=false;++if(task_isolation_on_cpu(cpu)){+/*+*CPUisrunningisolatedtask.Sinceitmaylose+*isolationandre-enterkernelsimultaneouslywith+*thisupdate,disablerecordinguntilit'sdone.+*/+atomic_inc(&cpu_buffer->record_disabled);+/* Make sure, update is done, and isolation state is current */+smp_mb();+if(task_isolation_on_cpu(cpu)){+/*+*IfCPUisstillrunningisolatedtask,we+*canbesurethatbreakingisolationwill+*happenwhilerecordingisdisabled,andCPU+*willnottouchthisbufferuntiltheupdate+*isdone.+*/+rb_update_pages(cpu_buffer);+cpu_buffer->nr_pages_to_update=0;+rv=true;+}+atomic_dec(&cpu_buffer->record_disabled);+}+returnrv;+}+/***ring_buffer_resize-resizetheringbuffer*@buffer:thebuffertoresize.
@@ -1784,13 +1816,22 @@ int ring_buffer_resize(struct trace_buffer *buffer, unsigned long size,if(!cpu_buffer->nr_pages_to_update)continue;-/* Can't run something on an offline CPU. */+/*+*Can'trunsomethingonanofflineCPU.+*+*CPUsrunningisolatedtasksdon'thaveto+*updateringbuffersuntiltheyexit+*isolationbecausetheyarein+*userspace.Usetheprocedurethatprevents+*raceconditionwithisolationbreaking.+*/if(!cpu_online(cpu)){rb_update_pages(cpu_buffer);cpu_buffer->nr_pages_to_update=0;}else{-schedule_work_on(cpu,-&cpu_buffer->update_pages_work);+if(!update_if_isolated(cpu_buffer,cpu))+schedule_work_on(cpu,+&cpu_buffer->update_pages_work);}}
@@ -1829,13 +1870,22 @@ int ring_buffer_resize(struct trace_buffer *buffer, unsigned long size,get_online_cpus();-/* Can't run something on an offline CPU. */+/*+*Can'trunsomethingonanofflineCPU.+*+*CPUsrunningisolatedtasksdon'thavetoupdate+*ringbuffersuntiltheyexitisolationbecausethey+*areinuserspace.Usetheprocedurethatprevents+*raceconditionwithisolationbreaking.+*/if(!cpu_online(cpu_id))rb_update_pages(cpu_buffer);else{-schedule_work_on(cpu_id,+if(!update_if_isolated(cpu_buffer,cpu_id))+schedule_work_on(cpu_id,&cpu_buffer->update_pages_work);-wait_for_completion(&cpu_buffer->update_done);+wait_for_completion(&cpu_buffer->update_done);+}}cpu_buffer->nr_pages_to_update=0;
From: Alex Belits <hidden> Date: 2020-03-04 16:15:51
From: Yuri Norov <redacted>
Make sure that kick_all_cpus_sync() does not call CPUs that are running
isolated tasks.
Signed-off-by: Alex Belits <redacted>
---
kernel/smp.c | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
@@ -731,9 +731,21 @@ static void do_nothing(void *unused)*/voidkick_all_cpus_sync(void){+structcpumaskmask;+/* Make sure the change is visible before we kick the cpus */smp_mb();-smp_call_function(do_nothing,NULL,1);++preempt_disable();+#ifdef CONFIG_TASK_ISOLATION+cpumask_clear(&mask);+task_isolation_cpumask(&mask);+cpumask_complement(&mask,&mask);+#else+cpumask_setall(&mask);+#endif+smp_call_function_many(&mask,do_nothing,NULL,1);+preempt_enable();}EXPORT_SYMBOL_GPL(kick_all_cpus_sync);
On Wed, Mar 04, 2020 at 04:15:24PM +0000, Alex Belits wrote:
quoted hunk
From: Yuri Norov <redacted>
Make sure that kick_all_cpus_sync() does not call CPUs that are running
isolated tasks.
Signed-off-by: Alex Belits <redacted>
---
kernel/smp.c | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
@@ -731,9 +731,21 @@ static void do_nothing(void *unused)*/voidkick_all_cpus_sync(void){+structcpumaskmask;+/* Make sure the change is visible before we kick the cpus */smp_mb();-smp_call_function(do_nothing,NULL,1);++preempt_disable();+#ifdef CONFIG_TASK_ISOLATION+cpumask_clear(&mask);+task_isolation_cpumask(&mask);+cpumask_complement(&mask,&mask);+#else+cpumask_setall(&mask);+#endif+smp_call_function_many(&mask,do_nothing,NULL,1);+preempt_enable();}
That looks very dangerous, the callers of kick_all_cpus_sync() want to
sync all CPUs for a reason. You will rather need to fix the callers.
Thanks.
From: Alex Belits <hidden> Date: 2020-03-08 06:49:51
On Fri, 2020-03-06 at 16:34 +0100, Frederic Weisbecker wrote:
On Wed, Mar 04, 2020 at 04:15:24PM +0000, Alex Belits wrote:
quoted
From: Yuri Norov <redacted>
Make sure that kick_all_cpus_sync() does not call CPUs that are
running
isolated tasks.
Signed-off-by: Alex Belits <redacted>
---
kernel/smp.c | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
@@ -731,9 +731,21 @@ static void do_nothing(void *unused)*/voidkick_all_cpus_sync(void){+structcpumaskmask;+/* Make sure the change is visible before we kick the cpus */smp_mb();-smp_call_function(do_nothing,NULL,1);++preempt_disable();+#ifdef CONFIG_TASK_ISOLATION+cpumask_clear(&mask);+task_isolation_cpumask(&mask);+cpumask_complement(&mask,&mask);+#else+cpumask_setall(&mask);+#endif+smp_call_function_many(&mask,do_nothing,NULL,1);+preempt_enable();}
That looks very dangerous, the callers of kick_all_cpus_sync() want
to
sync all CPUs for a reason. You will rather need to fix the callers.
All callers of this use this function to synchronize IPIs and icache,
and they have no idea if there is anything special about the state of
CPUs. If a task is isolated, this call would not be necessary because
the task is in userspace, and it would have to enter kernel for any of
that to become relevant but then it will have to switch from userspace
to kernel. At worst it is returning to userspace after entering
isolation or back in kernel running cleanup after isolation is broken
but before tsk_thread_flags_cache is updated. There will be nothing to
run on the same CPU because we have just left isolation, so task will
either exit or go back to userspace.
Is there any reason for a race at that point?
On Sun, Mar 08, 2020 at 06:48:43AM +0000, Alex Belits wrote:
On Fri, 2020-03-06 at 16:34 +0100, Frederic Weisbecker wrote:
quoted
On Wed, Mar 04, 2020 at 04:15:24PM +0000, Alex Belits wrote:
quoted
From: Yuri Norov <redacted>
Make sure that kick_all_cpus_sync() does not call CPUs that are
running
isolated tasks.
Signed-off-by: Alex Belits <redacted>
---
kernel/smp.c | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
@@ -731,9 +731,21 @@ static void do_nothing(void *unused)*/voidkick_all_cpus_sync(void){+structcpumaskmask;+/* Make sure the change is visible before we kick the cpus */smp_mb();-smp_call_function(do_nothing,NULL,1);++preempt_disable();+#ifdef CONFIG_TASK_ISOLATION+cpumask_clear(&mask);+task_isolation_cpumask(&mask);+cpumask_complement(&mask,&mask);+#else+cpumask_setall(&mask);+#endif+smp_call_function_many(&mask,do_nothing,NULL,1);+preempt_enable();}
That looks very dangerous, the callers of kick_all_cpus_sync() want
to
sync all CPUs for a reason. You will rather need to fix the callers.
All callers of this use this function to synchronize IPIs and icache,
and they have no idea if there is anything special about the state of
CPUs. If a task is isolated, this call would not be necessary because
the task is in userspace, and it would have to enter kernel for any of
that to become relevant but then it will have to switch from userspace
to kernel. At worst it is returning to userspace after entering
isolation or back in kernel running cleanup after isolation is broken
but before tsk_thread_flags_cache is updated. There will be nothing to
run on the same CPU because we have just left isolation, so task will
either exit or go back to userspace.
Is there any reason for a race at that point?
I can imagine several races:
1) The isolated task has set the cpumask but hasn't exited the kernel
yet. If it still runs kernel code while kick_all_cpus_sync() has completed,
we fail.
2) The isolated task is running do_exit() but the caller of kick_all_cpus_sync()
still sees the target as part of the isolated mask.
3) The isolated task has just set the isolated cpumask and entered userspace
but the caller still don't see the new value in the isolated cpumask, so it sends
the IPI to the isolated CPU.
Besides, any caller of kick_all_cpus_sync() is in its right to expect that
everything preceding the call to that function is visible to all CPUs
after that call. If you spare that IPI to an isolated CPU, what ensures
it will see what it is supposed to once it calls do_exit() or prctl()?
Is there a way we could fix the callers instead? For example synchronize_rcu()
could be a replacement (it handles very well nohz_full CPUs), provided the
callsites can sleep. It seems to be the case for __do_tune_cpucache() at least.
flush_icache_range() is scarier I have to admit, doesn't look like it can
sleep.
From: Alex Belits <hidden> Date: 2020-03-04 16:16:47
There are various mechanisms that select CPUs for jobs other than
regular workqueue selection. CPU isolation normally does not
prevent those jobs from running on isolated CPUs. When task
isolation is enabled those jobs should be limited to housekeeping
CPUs.
Signed-off-by: Alex Belits <redacted>
---
drivers/pci/pci-driver.c | 9 +++++++
lib/cpumask.c | 53 +++++++++++++++++++++++++---------------
net/core/net-sysfs.c | 9 +++++++
3 files changed, 51 insertions(+), 20 deletions(-)
From: Alex Belits <hidden> Date: 2020-03-08 03:44:02
This is the updated version of task isolation patchset.
1. Commit messages updated to match changes.
2. Sign-off lines restored from original patches, changes listed wherever applicable.
3. arm platform -- added missing calls to syscall check and cleanup procedure after leaving isolation.
4. x86 platform -- added missing calls to cleanup procedure after leaving isolation.
From: Alex Belits <hidden> Date: 2020-03-08 03:44:57
From: Chris Metcalf <redacted>
In commit f01f17d3705b ("mm, vmstat: make quiet_vmstat lighter")
the quiet_vmstat() function became asynchronous, in the sense that
the vmstat work was still scheduled to run on the core when the
function returned. For task isolation, we need a synchronous
version of the function that guarantees that the vmstat worker
will not run on the core on return from the function. Add a
quiet_vmstat_sync() function with that semantic.
Signed-off-by: Chris Metcalf <redacted>
Signed-off-by: Alex Belits <redacted>
---
include/linux/vmstat.h | 2 ++
mm/vmstat.c | 9 +++++++++
2 files changed, 11 insertions(+)
From: Alex Belits <hidden> Date: 2020-03-08 03:46:55
From: Chris Metcalf <redacted>
This function checks to see if a vmstat worker is not running,
and the vmstat diffs don't require an update. The function is
called from the task-isolation code to see if we need to
actually do some work to quiet vmstat.
Signed-off-by: Chris Metcalf <redacted>
Signed-off-by: Alex Belits <redacted>
---
include/linux/vmstat.h | 2 ++
mm/vmstat.c | 10 ++++++++++
2 files changed, 12 insertions(+)
currently:
+ * no vmstat worker running and no vmstat updates to perform.
+ */
+bool vmstat_idle(void)
+{
+ return !delayed_work_pending(this_cpu_ptr(&vmstat_work)) &&
+ !need_update(smp_processor_id());
+}
+
/*
* Shepherd worker thread that checks the
* differentials of processors that have their worker
--
2.20.1
From: Alex Belits <hidden> Date: 2020-03-08 03:48:02
The existing nohz_full mode is designed as a "soft" isolation mode
that makes tradeoffs to minimize userspace interruptions while
still attempting to avoid overheads in the kernel entry/exit path,
to provide 100% kernel semantics, etc.
However, some applications require a "hard" commitment from the
kernel to avoid interruptions, in particular userspace device driver
style applications, such as high-speed networking code.
This change introduces a framework to allow applications
to elect to have the "hard" semantics as needed, specifying
prctl(PR_TASK_ISOLATION, PR_TASK_ISOLATION_ENABLE) to do so.
The kernel must be built with the new TASK_ISOLATION Kconfig flag
to enable this mode, and the kernel booted with an appropriate
"isolcpus=nohz,domain,CPULIST" boot argument to enable
nohz_full and isolcpus. The "task_isolation" state is then indicated
by setting a new task struct field, task_isolation_flag, to the
value passed by prctl(), and also setting a TIF_TASK_ISOLATION
bit in the thread_info flags. When the kernel is returning to
userspace from the prctl() call and sees TIF_TASK_ISOLATION set,
it calls the new task_isolation_start() routine to arrange for
the task to avoid being interrupted in the future.
With interrupts disabled, task_isolation_start() ensures that kernel
subsystems that might cause a future interrupt are quiesced. If it
doesn't succeed, it adjusts the syscall return value to indicate that
fact, and userspace can retry as desired. In addition to stopping
the scheduler tick, the code takes any actions that might avoid
a future interrupt to the core, such as a worker thread being
scheduled that could be quiesced now (e.g. the vmstat worker)
or a future IPI to the core to clean up some state that could be
cleaned up now (e.g. the mm lru per-cpu cache).
Once the task has returned to userspace after issuing the prctl(),
if it enters the kernel again via system call, page fault, or any
other exception or irq, the kernel will kill it with SIGKILL.
In addition to sending a signal, the code supports a kernel
command-line "task_isolation_debug" flag which causes a stack
backtrace to be generated whenever a task loses isolation.
To allow the state to be entered and exited, the syscall checking
test ignores the prctl(PR_TASK_ISOLATION) syscall so that we can
clear the bit again later, and ignores exit/exit_group to allow
exiting the task without a pointless signal being delivered.
The prctl() API allows for specifying a signal number to use instead
of the default SIGKILL, to allow for catching the notification
signal; for example, in a production environment, it might be
helpful to log information to the application logging mechanism
before exiting. Or, the signal handler might choose to reset the
program counter back to the code segment intended to be run isolated
via prctl() to continue execution.
In a number of cases we can tell on a remote cpu that we are
going to be interrupting the cpu, e.g. via an IPI or a TLB flush.
In that case we generate the diagnostic (and optional stack dump)
on the remote core to be able to deliver better diagnostics.
If the interrupt is not something caught by Linux (e.g. a
hypervisor interrupt) we can also request a reschedule IPI to
be sent to the remote core so it can be sure to generate a
signal to notify the process.
Separate patches that follow provide these changes for x86, arm,
and arm64.
Signed-off-by: Alex Belits <redacted>
---
.../admin-guide/kernel-parameters.txt | 6 +
include/linux/hrtimer.h | 4 +
include/linux/isolation.h | 229 ++++++
include/linux/sched.h | 4 +
include/linux/tick.h | 3 +
include/uapi/linux/prctl.h | 6 +
init/Kconfig | 28 +
kernel/Makefile | 2 +
kernel/context_tracking.c | 2 +
kernel/isolation.c | 774 ++++++++++++++++++
kernel/signal.c | 2 +
kernel/sys.c | 6 +
kernel/time/hrtimer.c | 27 +
kernel/time/tick-sched.c | 18 +
14 files changed, 1111 insertions(+)
create mode 100644 include/linux/isolation.h
create mode 100644 kernel/isolation.c
@@ -4808,6 +4808,12 @@ neutralize any effect of /proc/sys/kernel/sysrq. Useful for debugging.+ task_isolation_debug [KNL]+ In kernels built with CONFIG_TASK_ISOLATION, this+ setting will generate console backtraces to+ accompany the diagnostics generated about+ interrupting tasks running with task isolation.+ tcpmhash_entries= [KNL,NET] Set the number of tcp_metrics_hash slots. Default value is 8192 or 16384 depending on total
@@ -576,6 +576,34 @@ config CPU_ISOLATIONsource"kernel/rcu/Kconfig"+configHAVE_ARCH_TASK_ISOLATION+bool++configTASK_ISOLATION+bool"Provide hard CPU isolation from the kernel on demand"+depends onNO_HZ_FULL&&HAVE_ARCH_TASK_ISOLATION+help++Allowuserspaceprocessesthatplacethemselvesoncoreswith+nohz_fullandisolcpusenabled,andrunprctl(PR_TASK_ISOLATION),+to"isolate"themselvesfromthekernel.Priortoreturningto+userspace,isolatedtaskswillarrangethatnofuturekernel+activitywillinterruptthetaskwhilethetaskisrunningin+userspace.Attemptingtore-enterthekernelwhileinthismode+willcausethetasktobeterminatedwithasignal;youmust+explicitlyuseprctl()todisabletaskisolationbeforeresuming+normaluseofthekernel.++This"hard"isolationfromthekernelisrequiredforuserspace+tasksthatarerunninghardreal-timetasksinuserspace,suchas+ahigh-speednetworkdriverinuserspace.Withoutthisoption,but+withNO_HZ_FULLenabled,thekernelwillmakeabest-faith,"soft"+efforttoshieldasingleuserspaceprocessfrominterrupts,but+makesnoguarantees.++Youshouldsay"N"unlessyouareintendingtoruna+high-performanceuserspacedriverorsimilartask.+configBUILD_BIN2Cbooldefaultn
@@ -0,0 +1,774 @@+// SPDX-License-Identifier: GPL-2.0-only+/*+*linux/kernel/isolation.c+*+*Implementationoftaskisolation.+*+*Authors:+*ChrisMetcalf<cmetcalf@mellanox.com>+*AlexBelits<abelits@marvell.com>+*YuriNorov<ynorov@marvell.com>+*/++#include<linux/mm.h>+#include<linux/swap.h>+#include<linux/vmstat.h>+#include<linux/sched.h>+#include<linux/isolation.h>+#include<linux/syscalls.h>+#include<linux/smp.h>+#include<linux/tick.h>+#include<asm/unistd.h>+#include<asm/syscall.h>+#include<linux/hrtimer.h>++/*+*Thesevaluesarestoredintask_isolation_state.+*NotethatSTATE_NORMAL+TIF_TASK_ISOLATIONmeanswearestill+*returningfromsys_prctl()touserspace.+*/+enum{+STATE_NORMAL=0,/* Not isolated */+STATE_ISOLATED=1/* In userspace, isolated */+};++/*+*Thisvariablecontainsthreadflagscopiedatthemoment+*whenschedule()switchedtothetaskonagivenCPU,+*or0ifnotaskisrunning.+*/+DEFINE_PER_CPU(unsignedlong,tsk_thread_flags_cache);++/*+*CounterforisolationstateonagivenCPU,incrementswhenentering+*isolationanddecrementswhenexitingisolation(beforeorafterthe+*cleanup).Multiplesimultaneouslyrunningproceduresenteringor+*exitingisolationarepreventedbycheckingtheresultof+*incrementingordecrementingthisvariable.Thisvariableisboth+*incrementedanddecrementedbyCPUthatcausedisolationenteringor+*exit.+*+*Thisisnecessarybecausemultipleisolation-breakingeventsmayhappen+*atonce(oroneastheresultoftheother),howeverisolationexit+*mayonlyhappenoncetotransitionfromisolatedtonon-isolatedstate.+*Therefore,ifdecrementingthiscounterresultsinavaluelessthan0,+*isolationexitprocedurecan'tbestarted--italreadyhappened,oris+*inprogress,orisolationisnotenteredyet.+*/+DEFINE_PER_CPU(atomic_t,isol_counter);++/*+*DescriptionofthelasttwotasksthatranisolatedonagivenCPU.+*Thisisintendedonlyformessagesaboutisolationbreaking.We+*don'twantanyreferencestoactualtaskwhileaccessingthisfrom+*CPUthatcausedisolationbreaking--weknownothingabouttiming+*anddon'twanttouselockingorRCU.+*/+structisol_task_desc{+atomic_tcurr_index;+atomic_tcurr_index_wr;+boolwarned[2];+pid_tpid[2];+pid_ttgid[2];+charcomm[2][TASK_COMM_LEN];+};+staticDEFINE_PER_CPU(structisol_task_desc,isol_task_descs);++/*+*Counterforisolationexitingprocedures(fromrequesttothestartof+*cleanup)beingattemptedatonceonaCPU.Normallyincrementingof+*thiscounterisperformedfromtheCPUthatcausedisolationbreaking,+*howeverdecrementingisdonefromthecleanupprocedure,delegatedto+*theCPUthatisexitingisolation,notfromtheCPUthatcausedisolation+*breaking.+*+*Ifincrementingthiscounterwhilestartingisolationexitprocedure+*resultsinavaluegreaterthan0,isolationexitingisalreadyin+*progress,andcleanupdidnotstartyet.Thismeans,countershouldbe+*decrementedback,andisolationexitthatisalreadyinprogress,should+*beallowedtocomplete.Otherwise,anewisolationexitprocedureshould+*bestarted.+*/+DEFINE_PER_CPU(atomic_t,isol_exit_counter);++/*+*Descriptorforisolation-breakingSMPcalls+*/+DEFINE_PER_CPU(call_single_data_t,isol_break_csd);++cpumask_var_ttask_isolation_map;+cpumask_var_ttask_isolation_cleanup_map;+staticDEFINE_SPINLOCK(task_isolation_cleanup_lock);++/* We can run on cpus that are isolated from the scheduler and are nohz_full. */+staticint__inittask_isolation_init(void)+{+alloc_bootmem_cpumask_var(&task_isolation_cleanup_map);+if(alloc_cpumask_var(&task_isolation_map,GFP_KERNEL))+/*+*Atthispointtaskisolationshouldmatch+*nohz_full.Thismaychangeinthefuture.+*/+cpumask_copy(task_isolation_map,tick_nohz_full_mask);+return0;+}+core_initcall(task_isolation_init)++/* Enable stack backtraces of any interrupts of task_isolation cores. */+staticbooltask_isolation_debug;+staticint__inittask_isolation_debug_func(char*str)+{+task_isolation_debug=true;+return1;+}+__setup("task_isolation_debug",task_isolation_debug_func);++/*+*Recordname,pidandgrouppidofthetaskenteringisolationon+*thecurrentCPU.+*/+staticvoidrecord_curr_isolated_task(void)+{+intind;+intcpu=smp_processor_id();+structisol_task_desc*desc=&per_cpu(isol_task_descs,cpu);+structtask_struct*task=current;++/* Finish everything before recording current task */+smp_mb();+ind=atomic_inc_return(&desc->curr_index_wr)&1;+desc->comm[ind][sizeof(task->comm)-1]='\0';+memcpy(desc->comm[ind],task->comm,sizeof(task->comm)-1);+desc->pid[ind]=task->pid;+desc->tgid[ind]=task->tgid;+desc->warned[ind]=false;+/* Write everything, to be seen by other CPUs */+smp_mb();+atomic_inc(&desc->curr_index);+/* Everyone will see the new record from this point */+smp_mb();+}++/*+*Printmessageprefixedwiththedescriptionofthecurrent(or+*last)isolatedtaskonagivenCPU.Intendedforisolationbreaking+*messagesthatincludetargettaskfortheuser'sconvenience.+*+*Messagesproducedwiththisfunctionmayhaveobsoletetask+*informationifisolatedtasksmanagedtoexit,startandenter+*isolationmultipletimes,ormultipletaskstriedtoenter+*isolationonthesameCPUatonce.Forthoseunusualcasesitwould+*containavaliddescriptionofthecauseforisolationbreakingand+*targetCPUnumber,justnotthecorrectdescriptionofwhichtask+*endeduplosingisolation.+*/+inttask_isolation_message(intcpu,intlevel,boolsupp,constchar*fmt,...)+{+structisol_task_desc*desc;+structtask_struct*task;+va_listargs;+charbuf_prefix[TASK_COMM_LEN+20+3*20];+charbuf[200];+intcurr_cpu,ind_counter,ind_counter_old,ind;++curr_cpu=get_cpu();+desc=&per_cpu(isol_task_descs,cpu);+ind_counter=atomic_read(&desc->curr_index);++if(curr_cpu==cpu){+/*+*MessageisforthecurrentCPUsocurrent+*task_structshouldbeusedinsteadofcached+*information.+*+*Likeinotherdiagnosticmessages,ifissuedfrom+*interruptcontext,currentwillbetheinterrupted+*task.Unlikeotherdiagnosticmessages,thisis+*alwaysrelevantbecausethemessageisabout+*interruptingatask.+*/+ind=ind_counter&1;+if(supp&&desc->warned[ind]){+/*+*Ifsuppistrue,skipthemessageifthe+*sametaskwasmentionedinthemessage+*originatedonremoteCPU,anditdidnot+*re-enterisolatedstatesincethen(warned+*istrue).Onlylocalmessagesfollowing+*remotemessages,likelyaboutthesame+*isolationbreakingevent,areskippedto+*avoidduplication.Ifremotecauseis+*immediatelyfollowedbyalocalonebefore+*isolationisbroken,localcauseisskipped+*frommessages.+*/+put_cpu();+return0;+}+task=current;+snprintf(buf_prefix,sizeof(buf_prefix),+"isolation %s/%d/%d (cpu %d)",+task->comm,task->tgid,task->pid,cpu);+put_cpu();+}else{+/*+*MessageisforremoteCPU,usecachedinformation.+*/+put_cpu();+/*+*Makesure,indexremainedunchangedwhiledatawas+*copied.Ifitchanged,datathatwascopiedmaybe+*inconsistentbecausetwoupdatesinasequencecould+*overwritethedatawhileitwasbeingread.+*/+do{+/* Make sure we are reading up to date values */+smp_mb();+ind=ind_counter&1;+snprintf(buf_prefix,sizeof(buf_prefix),+"isolation %s/%d/%d (cpu %d)",+desc->comm[ind],desc->tgid[ind],+desc->pid[ind],cpu);+desc->warned[ind]=true;+ind_counter_old=ind_counter;+/* Record the warned flag, then re-read descriptor */+smp_mb();+ind_counter=atomic_read(&desc->curr_index);+/*+*Ifthecounterchanged,somethingwasupdated,so+*repeateverythingtogetthecurrentdata+*/+}while(ind_counter!=ind_counter_old);+}++va_start(args,fmt);+vsnprintf(buf,sizeof(buf),fmt,args);+va_end(args);++switch(level){+caseLOGLEVEL_EMERG:+pr_emerg("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_ALERT:+pr_alert("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_CRIT:+pr_crit("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_ERR:+pr_err("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_WARNING:+pr_warn("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_NOTICE:+pr_notice("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_INFO:+pr_info("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_DEBUG:+pr_debug("%s: %s",buf_prefix,buf);+break;+default:+/* No message without a valid level */+return0;+}+return1;+}++/*+*Dumpstackifneedbe.Thiscanbehelpfulevenfromthefinalexit+*tousermodecodesincestacktracessometimescarryinformationabout+*whatputyouintothekernel,e.g.aninterruptnumberencodedin+*theinitialentrystackframethatisstillvisibleatexittime.+*/+staticvoiddebug_dump_stack(void)+{+if(task_isolation_debug)+dump_stack();+}++/*+*Settheflagswordbutdon'ttrytoactuallystarttaskisolationyet.+*Wewillstartitwhenenteringuserspaceintask_isolation_start().+*/+inttask_isolation_request(unsignedintflags)+{+structtask_struct*task=current;++/*+*Thetaskisolationflagsshouldalwaysbeclearedjustby+*virtueofhavingenteredthekernel.+*/+WARN_ON_ONCE(test_tsk_thread_flag(task,TIF_TASK_ISOLATION));+WARN_ON_ONCE(task->task_isolation_flags!=0);+WARN_ON_ONCE(task->task_isolation_state!=STATE_NORMAL);++task->task_isolation_flags=flags;+if(!(task->task_isolation_flags&PR_TASK_ISOLATION_ENABLE))+return0;++/* We are trying to enable task isolation. */+set_tsk_thread_flag(task,TIF_TASK_ISOLATION);++/*+*Shutdownthevmstatworkersowe'renotinterruptedlater.+*Wehavetotrytodothishere(withinterruptsenabled)since+*wearecancelingdelayedworkandwillcallflush_work()+*(whichenablesinterrupts)andpossiblyschedule().+*/+quiet_vmstat_sync();++/* We return 0 here but we may change that in task_isolation_start(). */+return0;+}++/*+*Performactionsthatshouldbedoneimmediatelyonexitfromisolation.+*/+staticvoidfast_task_isolation_cpu_cleanup(void*info)+{+atomic_dec(&per_cpu(isol_exit_counter,smp_processor_id()));+/* At this point breaking isolation from other CPUs is possible again */++/*+*Thistaskisnolongerisolated(andifbyanychancethis+*isthewrongtask,it'salreadynotisolated)+*/+current->task_isolation_flags=0;+clear_tsk_thread_flag(current,TIF_TASK_ISOLATION);++/* Run the rest of cleanup later */+set_tsk_thread_flag(current,TIF_NOTIFY_RESUME);++/* Copy flags with task isolation disabled */+this_cpu_write(tsk_thread_flags_cache,+READ_ONCE(task_thread_info(current)->flags));+}++/* Disable task isolation for the specified task. */+staticvoidstop_isolation(structtask_struct*p)+{+intcpu,this_cpu;+unsignedlongflags;++this_cpu=get_cpu();+cpu=task_cpu(p);+if(atomic_inc_return(&per_cpu(isol_exit_counter,cpu))>1){+/* Already exiting isolation */+atomic_dec(&per_cpu(isol_exit_counter,cpu));+put_cpu();+return;+}++if(p==current){+p->task_isolation_state=STATE_NORMAL;+fast_task_isolation_cpu_cleanup(NULL);+task_isolation_cpu_cleanup();+if(atomic_dec_return(&per_cpu(isol_counter,cpu))<0){+/* Is not isolated already */+atomic_inc(&per_cpu(isol_counter,cpu));+}+put_cpu();+}else{+if(atomic_dec_return(&per_cpu(isol_counter,cpu))<0){+/* Is not isolated already */+atomic_inc(&per_cpu(isol_counter,cpu));+atomic_dec(&per_cpu(isol_exit_counter,cpu));+put_cpu();+return;+}+/*+*Schedule"slow"cleanup.Thisrelieson+*TIF_NOTIFY_RESUMEbeingset+*/+spin_lock_irqsave(&task_isolation_cleanup_lock,flags);+cpumask_set_cpu(cpu,task_isolation_cleanup_map);+spin_unlock_irqrestore(&task_isolation_cleanup_lock,flags);+/*+*SettingflagsisdelegatedtotheCPUwhere+*isolatedtaskisrunning+*isol_exit_counterwillbedecrementedfromthereaswell.+*/+per_cpu(isol_break_csd,cpu).func=+fast_task_isolation_cpu_cleanup;+per_cpu(isol_break_csd,cpu).info=NULL;+per_cpu(isol_break_csd,cpu).flags=0;+smp_call_function_single_async(cpu,+&per_cpu(isol_break_csd,cpu));+put_cpu();+}+}++/*+*Thiscoderunswithinterruptsdisabledjustbeforethereturnto+*userspace,afteraprctl()hasrequestedenablingtaskisolation.+*Wetakewhateverstepsareneededtoavoidbeinginterruptedlater:+*drainthelrupages,stoptheschedulertick,etc.More+*functionalitymaybeaddedherelatertoavoidothertypesof+*interruptsfromotherkernelsubsystems.+*+*Ifwecan'tenabletaskisolation,weupdatethesyscallreturn+*valuewithanappropriateerror.+*/+voidtask_isolation_start(void)+{+interror;++/*+*WeshouldonlybecalledinSTATE_NORMAL(isolationdisabled),+*onourwayoutofthekernelfromtheprctl()thatturnediton.+*Ifweareexitingfromthekernelinanotherstate,itmeanswe+*madeitbackintothekernelwithoutdisablingtaskisolation,+*andweshouldinvestigatehow(andinanycasedisabletask+*isolationatthispoint).Weareclearlynotonthepathback+*fromtheprctl()sowedon'ttouchthesyscallreturnvalue.+*/+if(WARN_ON_ONCE(current->task_isolation_state!=STATE_NORMAL)){+/* Increment counter, this will allow isolation breaking */+if(atomic_inc_return(&per_cpu(isol_counter,+smp_processor_id()))>1){+atomic_dec(&per_cpu(isol_counter,smp_processor_id()));+}+atomic_inc(&per_cpu(isol_counter,smp_processor_id()));+stop_isolation(current);+return;+}++/*+*Mustbeaffinitizedtoasinglecorewithtaskisolationpossible.+*Inprinciplethiscouldberemotelymodifiedbetweentheprctl()+*andthereturntouserspace,sowehavetocheckithere.+*/+if(current->nr_cpus_allowed!=1||+!is_isolation_cpu(smp_processor_id())){+error=-EINVAL;+gotoerror;+}++/* If the vmstat delayed work is not canceled, we have to try again. */+if(!vmstat_idle()){+error=-EAGAIN;+gotoerror;+}++/* Try to stop the dynamic tick. */+error=try_stop_full_tick();+if(error)+gotoerror;++/* Drain the pagevecs to avoid unnecessary IPI flushes later. */+lru_add_drain();++/* Increment counter, this will allow isolation breaking */+if(atomic_inc_return(&per_cpu(isol_counter,+smp_processor_id()))>1){+atomic_dec(&per_cpu(isol_counter,smp_processor_id()));+}++/* Record isolated task IDs and name */+record_curr_isolated_task();++/* Copy flags with task isolation enabled */+this_cpu_write(tsk_thread_flags_cache,+READ_ONCE(task_thread_info(current)->flags));++current->task_isolation_state=STATE_ISOLATED;+return;++error:+/* Increment counter, this will allow isolation breaking */+if(atomic_inc_return(&per_cpu(isol_counter,+smp_processor_id()))>1){+atomic_dec(&per_cpu(isol_counter,smp_processor_id()));+}+stop_isolation(current);+syscall_set_return_value(current,current_pt_regs(),error,0);+}++/* Stop task isolation on the remote task and send it a signal. */+staticvoidsend_isolation_signal(structtask_struct*task)+{+intflags=task->task_isolation_flags;+kernel_siginfo_tinfo={+.si_signo=PR_TASK_ISOLATION_GET_SIG(flags)?:SIGKILL,+};++stop_isolation(task);+send_sig_info(info.si_signo,&info,task);+}++/* Only a few syscalls are valid once we are in task isolation mode. */+staticboolis_acceptable_syscall(intsyscall)+{+/* No need to incur an isolation signal if we are just exiting. */+if(syscall==__NR_exit||syscall==__NR_exit_group)+returntrue;++/* Check to see if it's the prctl for isolation. */+if(syscall==__NR_prctl){+unsignedlongarg[SYSCALL_MAX_ARGS];++syscall_get_arguments(current,current_pt_regs(),arg);+if(arg[0]==PR_TASK_ISOLATION)+returntrue;+}++returnfalse;+}++/*+*Thisroutineiscalledfromsyscallentry,preventsmostsyscalls+*fromexecuting,andifneededraisesasignaltonotifytheprocess.+*+*Notethatwehavetostopisolationbeforeweevenprintamessage+*here,sinceotherwisewemightendupreportinganinterruptdueto+*kickingtheprintkhandlingcode,ratherthanreportingthetrue+*causeofinterrupthere.+*+*Themessageisnotsuppressedbypreviousremotelytriggered+*messages.+*/+inttask_isolation_syscall(intsyscall)+{+structtask_struct*task=current;++if(is_acceptable_syscall(syscall)){+stop_isolation(task);+return0;+}++send_isolation_signal(task);++pr_task_isol_warn(smp_processor_id(),+"task_isolation lost due to syscall %d\n",+syscall);+debug_dump_stack();++syscall_set_return_value(task,current_pt_regs(),-ERESTARTNOINTR,-1);+return-1;+}++/*+*Thisroutineiscalledfromanyexceptionorirqthatdoesn't+*otherwisetriggerasignaltotheuserprocess(e.g.pagefault).+*+*Messageswillbesuppressedifthereisalreadyareportedremote+*causeforisolationbreaking,sowedon'tgeneratemultiple+*confusinglysimilarmessagesaboutthesameevent.+*/+void_task_isolation_interrupt(constchar*fmt,...)+{+structtask_struct*task=current;+va_listargs;+charbuf[100];++/* RCU should have been enabled prior to this point. */+RCU_LOCKDEP_WARN(!rcu_is_watching(),"kernel entry without RCU");++/* Are we exiting isolation already? */+if(atomic_read(&per_cpu(isol_exit_counter,smp_processor_id()))!=0){+task->task_isolation_state=STATE_NORMAL;+return;+}+/*+*Avoidreportinginterruptsthathappenafterwehaveprctl'ed+*toenableisolation,butbeforewehavereturnedtouserspace.+*/+if(task->task_isolation_state==STATE_NORMAL)+return;++va_start(args,fmt);+vsnprintf(buf,sizeof(buf),fmt,args);+va_end(args);++/* Handle NMIs minimally, since we can't send a signal. */+if(in_nmi()){+pr_task_isol_err(smp_processor_id(),+"isolation: in NMI; not delivering signal\n");+}else{+send_isolation_signal(task);+}++if(pr_task_isol_warn_supp(smp_processor_id(),+"task_isolation lost due to %s\n",buf))+debug_dump_stack();+}++/*+*Calledbeforewewakeupataskthathasasignaltoprocess.+*Needstobedonetohandleinterruptsthattriggersignals,which+*wedon'tcatchwithtask_isolation_interrupt()hooks.+*+*Thismessageisalsosuppressediftherewasalreadyaremotely+*causedmessageaboutthesameisolationbreakingevent.+*/+void_task_isolation_signal(structtask_struct*task)+{+structisol_task_desc*desc;+intind,cpu;+booldo_warn=(task->task_isolation_state==STATE_ISOLATED);++cpu=task_cpu(task);+desc=&per_cpu(isol_task_descs,cpu);+ind=atomic_read(&desc->curr_index)&1;+if(desc->warned[ind])+do_warn=false;++stop_isolation(task);++if(do_warn){+pr_warn("isolation: %s/%d/%d (cpu %d): task_isolation lost due to signal\n",+task->comm,task->tgid,task->pid,cpu);+debug_dump_stack();+}+}++/*+*Generateastackbacktraceifwearegoingtointerruptanothertask+*isolationprocess.+*/+voidtask_isolation_remote(intcpu,constchar*fmt,...)+{+structtask_struct*curr_task;+va_listargs;+charbuf[200];++if(!is_isolation_cpu(cpu)||!task_isolation_on_cpu(cpu))+return;++curr_task=current;++va_start(args,fmt);+vsnprintf(buf,sizeof(buf),fmt,args);+va_end(args);+if(pr_task_isol_warn(cpu,+"task_isolation lost due to %s by %s/%d/%d on cpu %d\n",+buf,+curr_task->comm,curr_task->tgid,+curr_task->pid,smp_processor_id()))+debug_dump_stack();+}++/*+*Generateastackbacktraceifanyofthecpusin"mask"arerunning+*taskisolationprocesses.+*/+voidtask_isolation_remote_cpumask(conststructcpumask*mask,+constchar*fmt,...)+{+structtask_struct*curr_task;+cpumask_var_twarn_mask;+va_listargs;+charbuf[200];+intcpu,first_cpu;++if(task_isolation_map==NULL||+!zalloc_cpumask_var(&warn_mask,GFP_KERNEL))+return;++first_cpu=-1;+for_each_cpu_and(cpu,mask,task_isolation_map){+if(task_isolation_on_cpu(cpu)){+if(first_cpu<0)+first_cpu=cpu;+else+cpumask_set_cpu(cpu,warn_mask);+}+}++if(first_cpu<0)+gotodone;++curr_task=current;++va_start(args,fmt);+vsnprintf(buf,sizeof(buf),fmt,args);+va_end(args);++if(cpumask_weight(warn_mask)==0)+pr_task_isol_warn(first_cpu,+"task_isolation lost due to %s by %s/%d/%d on cpu %d\n",+buf,curr_task->comm,curr_task->tgid,+curr_task->pid,smp_processor_id());+else+pr_task_isol_warn(first_cpu,+" and cpus %*pbl: task_isolation lost due to %s by %s/%d/%d on cpu %d\n",+cpumask_pr_args(warn_mask),+buf,curr_task->comm,curr_task->tgid,+curr_task->pid,smp_processor_id());+debug_dump_stack();++done:+free_cpumask_var(warn_mask);+}++/*+*CheckifgivenCPUisrunningisolatedtask.+*/+inttask_isolation_on_cpu(intcpu)+{+returntest_bit(TIF_TASK_ISOLATION,+&per_cpu(tsk_thread_flags_cache,cpu));+}++/*+*SetCPUscurrentlyrunningisolatedtasksinCPUmask.+*/+voidtask_isolation_cpumask(structcpumask*mask)+{+intcpu;++if(task_isolation_map==NULL)+return;++for_each_cpu(cpu,task_isolation_map)+if(task_isolation_on_cpu(cpu))+cpumask_set_cpu(cpu,mask);+}++/*+*ClearCPUscurrentlyrunningisolatedtasksinCPUmask.+*/+voidtask_isolation_clear_cpumask(structcpumask*mask)+{+intcpu;++if(task_isolation_map==NULL)+return;++for_each_cpu(cpu,task_isolation_map)+if(task_isolation_on_cpu(cpu))+cpumask_clear_cpu(cpu,mask);+}++/*+*Cleanupprocedure.Thecalltothisproceduremaybedelayed.+*/+voidtask_isolation_cpu_cleanup(void)+{+kick_hrtimer();+}++/*+*CheckifcleanupisscheduledonthecurrentCPU,andifso,runit.+*Intendedtobecalledfromnotify_resume()oranothersuchcallback+*onthetargetCPU.+*/+voidtask_isolation_check_run_cleanup(void)+{+intcpu;+unsignedlongflags;++spin_lock_irqsave(&task_isolation_cleanup_lock,flags);++cpu=smp_processor_id();++if(cpumask_test_cpu(cpu,task_isolation_cleanup_map)){+cpumask_clear_cpu(cpu,task_isolation_cleanup_map);+spin_unlock_irqrestore(&task_isolation_cleanup_lock,flags);+task_isolation_cpu_cleanup();+}else+spin_unlock_irqrestore(&task_isolation_cleanup_lock,flags);+}
@@ -882,6 +882,24 @@ static void tick_nohz_full_update_tick(struct tick_sched *ts)#endif}+#ifdef CONFIG_TASK_ISOLATION+inttry_stop_full_tick(void)+{+intcpu=smp_processor_id();+structtick_sched*ts=this_cpu_ptr(&tick_cpu_sched);++/* For an unstable clock, we should return a permanent error code. */+if(atomic_read(&tick_dep_mask)&TICK_DEP_MASK_CLOCK_UNSTABLE)+return-EINVAL;++if(!can_stop_full_tick(cpu,ts))+return-EAGAIN;++tick_nohz_stop_sched_tick(ts,cpu);+return0;+}+#endif+staticboolcan_stop_idle_tick(intcpu,structtick_sched*ts){/*
Thank you for resurrecting this code!
I have a question on the UAPI: the example code is using
PR_TASK_ISOLATION_USERSIG and it seems to be removed from this
version.
To enable isolation with SIGUSR1 the task should run:
prctl(PR_SET_TASK_ISOLATION, PR_TASK_ISOLATION_ENABLE
| PR_TASK_ISOLATION_SET_SIG(SIGUSR1), 0, 0, 0);
And to disable:
prctl(PR_SET_TASK_ISOLATION, 0, 0, 0, 0);
Is this correct?
Marta
On Sun, Mar 08, 2020 at 03:47:08AM +0000, Alex Belits wrote:
quoted hunk
The existing nohz_full mode is designed as a "soft" isolation mode
that makes tradeoffs to minimize userspace interruptions while
still attempting to avoid overheads in the kernel entry/exit path,
to provide 100% kernel semantics, etc.
However, some applications require a "hard" commitment from the
kernel to avoid interruptions, in particular userspace device driver
style applications, such as high-speed networking code.
This change introduces a framework to allow applications
to elect to have the "hard" semantics as needed, specifying
prctl(PR_TASK_ISOLATION, PR_TASK_ISOLATION_ENABLE) to do so.
The kernel must be built with the new TASK_ISOLATION Kconfig flag
to enable this mode, and the kernel booted with an appropriate
"isolcpus=nohz,domain,CPULIST" boot argument to enable
nohz_full and isolcpus. The "task_isolation" state is then indicated
by setting a new task struct field, task_isolation_flag, to the
value passed by prctl(), and also setting a TIF_TASK_ISOLATION
bit in the thread_info flags. When the kernel is returning to
userspace from the prctl() call and sees TIF_TASK_ISOLATION set,
it calls the new task_isolation_start() routine to arrange for
the task to avoid being interrupted in the future.
With interrupts disabled, task_isolation_start() ensures that kernel
subsystems that might cause a future interrupt are quiesced. If it
doesn't succeed, it adjusts the syscall return value to indicate that
fact, and userspace can retry as desired. In addition to stopping
the scheduler tick, the code takes any actions that might avoid
a future interrupt to the core, such as a worker thread being
scheduled that could be quiesced now (e.g. the vmstat worker)
or a future IPI to the core to clean up some state that could be
cleaned up now (e.g. the mm lru per-cpu cache).
Once the task has returned to userspace after issuing the prctl(),
if it enters the kernel again via system call, page fault, or any
other exception or irq, the kernel will kill it with SIGKILL.
In addition to sending a signal, the code supports a kernel
command-line "task_isolation_debug" flag which causes a stack
backtrace to be generated whenever a task loses isolation.
To allow the state to be entered and exited, the syscall checking
test ignores the prctl(PR_TASK_ISOLATION) syscall so that we can
clear the bit again later, and ignores exit/exit_group to allow
exiting the task without a pointless signal being delivered.
The prctl() API allows for specifying a signal number to use instead
of the default SIGKILL, to allow for catching the notification
signal; for example, in a production environment, it might be
helpful to log information to the application logging mechanism
before exiting. Or, the signal handler might choose to reset the
program counter back to the code segment intended to be run isolated
via prctl() to continue execution.
In a number of cases we can tell on a remote cpu that we are
going to be interrupting the cpu, e.g. via an IPI or a TLB flush.
In that case we generate the diagnostic (and optional stack dump)
on the remote core to be able to deliver better diagnostics.
If the interrupt is not something caught by Linux (e.g. a
hypervisor interrupt) we can also request a reschedule IPI to
be sent to the remote core so it can be sure to generate a
signal to notify the process.
Separate patches that follow provide these changes for x86, arm,
and arm64.
Signed-off-by: Alex Belits <redacted>
---
.../admin-guide/kernel-parameters.txt | 6 +
include/linux/hrtimer.h | 4 +
include/linux/isolation.h | 229 ++++++
include/linux/sched.h | 4 +
include/linux/tick.h | 3 +
include/uapi/linux/prctl.h | 6 +
init/Kconfig | 28 +
kernel/Makefile | 2 +
kernel/context_tracking.c | 2 +
kernel/isolation.c | 774 ++++++++++++++++++
kernel/signal.c | 2 +
kernel/sys.c | 6 +
kernel/time/hrtimer.c | 27 +
kernel/time/tick-sched.c | 18 +
14 files changed, 1111 insertions(+)
create mode 100644 include/linux/isolation.h
create mode 100644 kernel/isolation.c
@@ -4808,6 +4808,12 @@ neutralize any effect of /proc/sys/kernel/sysrq. Useful for debugging.+ task_isolation_debug [KNL]+ In kernels built with CONFIG_TASK_ISOLATION, this+ setting will generate console backtraces to+ accompany the diagnostics generated about+ interrupting tasks running with task isolation.+ tcpmhash_entries= [KNL,NET] Set the number of tcp_metrics_hash slots. Default value is 8192 or 16384 depending on total
gcc output:
In file included from ./arch/x86/include/asm/apic.h:6,
from arch/x86/kernel/apic/apic_noop.c:14:
./include/linux/isolation.h:58:32: error: unknown type name 'tsk_thread_flags_copy'
DECLARE_PER_CPU(unsigned long, tsk_thread_flags_copy);
^~~~~~~~~~~~~~~~~~~~~
My fix:
iff --git a/include/linux/isolation.h b/include/linux/isolation.h
index 6bd71c67f10f..a392abed304b 100644
+extern cpumask_var_t task_isolation_map;
+
+/**
+ * task_isolation_request() - prctl hook to request task isolation
+ * @flags: Flags from <linux/prctl.h> PR_TASK_ISOLATION_xxx.
+ *
+ * This is called from the generic prctl() code for PR_TASK_ISOLATION.
+ *
+ * Return: Returns 0 when task isolation enabled, otherwise a negative
+ * errno.
+ */
+extern int task_isolation_request(unsigned int flags);
+extern void task_isolation_cpu_cleanup(void);
+/**
+ * task_isolation_start() - attempt to actually start task isolation
+ *
+ * This function should be invoked as the last thing prior to returning to
+ * user space if TIF_TASK_ISOLATION is set in the thread_info flags. It
+ * will attempt to quiesce the core and enter task-isolation mode. If it
+ * fails, it will reset the system call return value to an error code that
+ * indicates the failure mode.
+ */
+extern void task_isolation_start(void);
+
+/**
+ * is_isolation_cpu() - check if CPU is intended for running isolated tasks.
+ * @cpu: CPU to check.
+ */
+static inline bool is_isolation_cpu(int cpu)
+{
+ return task_isolation_map != NULL &&
+ cpumask_test_cpu(cpu, task_isolation_map);
+}
+
+/**
+ * task_isolation_on_cpu() - check if the cpu is running isolated task
+ * @cpu: CPU to check.
+ */
+extern int task_isolation_on_cpu(int cpu);
+extern void task_isolation_check_run_cleanup(void);
+
+/**
+ * task_isolation_cpumask() - set CPUs currently running isolated tasks
+ * @mask: Mask to modify.
+ */
+extern void task_isolation_cpumask(struct cpumask *mask);
+
+/**
+ * task_isolation_clear_cpumask() - clear CPUs currently running isolated tasks
+ * @mask: Mask to modify.
+ */
+extern void task_isolation_clear_cpumask(struct cpumask *mask);
+
+/**
+ * task_isolation_syscall() - report a syscall from an isolated task
+ * @nr: The syscall number.
+ *
+ * This routine should be invoked at syscall entry if TIF_TASK_ISOLATION is
+ * set in the thread_info flags. It checks for valid syscalls,
+ * specifically prctl() with PR_TASK_ISOLATION, exit(), and exit_group().
+ * For any other syscall it will raise a signal and return failure.
+ *
+ * Return: 0 for acceptable syscalls, -1 for all others.
+ */
+extern int task_isolation_syscall(int nr);
+
+/**
+ * _task_isolation_interrupt() - report an interrupt of an isolated task
+ * @fmt: A format string describing the interrupt
+ * @...: Format arguments, if any.
+ *
+ * This routine should be invoked at any exception or IRQ if
+ * TIF_TASK_ISOLATION is set in the thread_info flags. It is not necessary
+ * to invoke it if the exception will generate a signal anyway (e.g. a bad
+ * page fault), and in that case it is preferable not to invoke it but just
+ * rely on the standard Linux signal. The macro task_isolation_syscall()
+ * wraps the TIF_TASK_ISOLATION flag test to simplify the caller code.
+ */
+extern void _task_isolation_interrupt(const char *fmt, ...);
+#define task_isolation_interrupt(fmt, ...) \
+ do { \
+ if (current_thread_info()->flags & _TIF_TASK_ISOLATION) \
+ _task_isolation_interrupt(fmt, ## __VA_ARGS__); \
+ } while (0)
+
+/**
+ * task_isolation_remote() - report a remote interrupt of an isolated task
+ * @cpu: The remote cpu that is about to be interrupted.
+ * @fmt: A format string describing the interrupt
+ * @...: Format arguments, if any.
+ *
+ * This routine should be invoked any time a remote IPI or other type of
+ * interrupt is being delivered to another cpu. The function will check to
+ * see if the target core is running a task-isolation task, and generate a
+ * diagnostic on the console if so; in addition, we tag the task so it
+ * doesn't generate another diagnostic when the interrupt actually arrives.
+ * Generating a diagnostic remotely yields a clearer indication of what
+ * happened then just reporting only when the remote core is interrupted.
+ *
+ */
+extern void task_isolation_remote(int cpu, const char *fmt, ...);
+
+/**
+ * task_isolation_remote_cpumask() - report interruption of multiple cpus
+ * @mask: The set of remotes cpus that are about to be interrupted.
+ * @fmt: A format string describing the interrupt
+ * @...: Format arguments, if any.
+ *
+ * This is the cpumask variant of _task_isolation_remote(). We
+ * generate a single-line diagnostic message even if multiple remote
+ * task-isolation cpus are being interrupted.
+ */
+extern void task_isolation_remote_cpumask(const struct cpumask *mask,
+ const char *fmt, ...);
+
+/**
+ * _task_isolation_signal() - disable task isolation when signal is pending
+ * @task: The task for which to disable isolation.
+ *
+ * This function generates a diagnostic and disables task isolation; it
+ * should be called if TIF_TASK_ISOLATION is set when notifying a task of a
+ * pending signal. The task_isolation_interrupt() function normally
+ * generates a diagnostic for events that just interrupt a task without
+ * generating a signal; here we need to hook the paths that correspond to
+ * interrupts that do generate a signal. The macro task_isolation_signal()
+ * wraps the TIF_TASK_ISOLATION flag test to simplify the caller code.
+ */
+extern void _task_isolation_signal(struct task_struct *task);
+#define task_isolation_signal(task) \
+ do { \
+ if (task_thread_info(task)->flags & _TIF_TASK_ISOLATION) \
+ _task_isolation_signal(task); \
+ } while (0)
+
+/**
+ * task_isolation_user_exit() - debug all user_exit calls
+ *
+ * By default, we don't generate an exception in the low-level user_exit()
+ * code, because programs lose the ability to disable task isolation: the
+ * user_exit() hook will cause a signal prior to task_isolation_syscall()
+ * disabling task isolation. In addition, it means that we lose all the
+ * diagnostic info otherwise available from task_isolation_interrupt() hooks
+ * later in the interrupt-handling process. But you may enable it here for
+ * a special kernel build if you are having undiagnosed userspace jitter.
+ */
+static inline void task_isolation_user_exit(void)
+{
+#ifdef DEBUG_TASK_ISOLATION
+ task_isolation_interrupt("user_exit");
+#endif
+}
+
+#else /* !CONFIG_TASK_ISOLATION */
+static inline int task_isolation_request(unsigned int flags) { return -EINVAL; }
+static inline void task_isolation_start(void) { }
+static inline bool is_isolation_cpu(int cpu) { return 0; }
+static inline int task_isolation_on_cpu(int cpu) { return 0; }
+static inline void task_isolation_cpumask(struct cpumask *mask) { }
+static inline void task_isolation_clear_cpumask(struct cpumask *mask) { }
+static inline void task_isolation_cpu_cleanup(void) { }
+static inline void task_isolation_check_run_cleanup(void) { }
+static inline int task_isolation_syscall(int nr) { return 0; }
+static inline void task_isolation_interrupt(const char *fmt, ...) { }
+static inline void task_isolation_remote(int cpu, const char *fmt, ...) { }
+static inline void task_isolation_remote_cpumask(const struct cpumask *mask,
+ const char *fmt, ...) { }
+static inline void task_isolation_signal(struct task_struct *task) { }
+static inline void task_isolation_user_exit(void) { }
+#endif
+
+#endif /* _LINUX_ISOLATION_H */
@@ -576,6 +576,34 @@ config CPU_ISOLATIONsource"kernel/rcu/Kconfig"+configHAVE_ARCH_TASK_ISOLATION+bool++configTASK_ISOLATION+bool"Provide hard CPU isolation from the kernel on demand"+depends onNO_HZ_FULL&&HAVE_ARCH_TASK_ISOLATION+help++Allowuserspaceprocessesthatplacethemselvesoncoreswith+nohz_fullandisolcpusenabled,andrunprctl(PR_TASK_ISOLATION),+to"isolate"themselvesfromthekernel.Priortoreturningto+userspace,isolatedtaskswillarrangethatnofuturekernel+activitywillinterruptthetaskwhilethetaskisrunningin+userspace.Attemptingtore-enterthekernelwhileinthismode+willcausethetasktobeterminatedwithasignal;youmust+explicitlyuseprctl()todisabletaskisolationbeforeresuming+normaluseofthekernel.++This"hard"isolationfromthekernelisrequiredforuserspace+tasksthatarerunninghardreal-timetasksinuserspace,suchas+ahigh-speednetworkdriverinuserspace.Withoutthisoption,but+withNO_HZ_FULLenabled,thekernelwillmakeabest-faith,"soft"+efforttoshieldasingleuserspaceprocessfrominterrupts,but+makesnoguarantees.++Youshouldsay"N"unlessyouareintendingtoruna+high-performanceuserspacedriverorsimilartask.+configBUILD_BIN2Cbooldefaultn
@@ -0,0 +1,774 @@+// SPDX-License-Identifier: GPL-2.0-only+/*+*linux/kernel/isolation.c+*+*Implementationoftaskisolation.+*+*Authors:+*ChrisMetcalf<cmetcalf@mellanox.com>+*AlexBelits<abelits@marvell.com>+*YuriNorov<ynorov@marvell.com>+*/++#include<linux/mm.h>+#include<linux/swap.h>+#include<linux/vmstat.h>+#include<linux/sched.h>+#include<linux/isolation.h>+#include<linux/syscalls.h>+#include<linux/smp.h>+#include<linux/tick.h>+#include<asm/unistd.h>+#include<asm/syscall.h>+#include<linux/hrtimer.h>++/*+*Thesevaluesarestoredintask_isolation_state.+*NotethatSTATE_NORMAL+TIF_TASK_ISOLATIONmeanswearestill+*returningfromsys_prctl()touserspace.+*/+enum{+STATE_NORMAL=0,/* Not isolated */+STATE_ISOLATED=1/* In userspace, isolated */+};++/*+*Thisvariablecontainsthreadflagscopiedatthemoment+*whenschedule()switchedtothetaskonagivenCPU,+*or0ifnotaskisrunning.+*/+DEFINE_PER_CPU(unsignedlong,tsk_thread_flags_cache);++/*+*CounterforisolationstateonagivenCPU,incrementswhenentering+*isolationanddecrementswhenexitingisolation(beforeorafterthe+*cleanup).Multiplesimultaneouslyrunningproceduresenteringor+*exitingisolationarepreventedbycheckingtheresultof+*incrementingordecrementingthisvariable.Thisvariableisboth+*incrementedanddecrementedbyCPUthatcausedisolationenteringor+*exit.+*+*Thisisnecessarybecausemultipleisolation-breakingeventsmayhappen+*atonce(oroneastheresultoftheother),howeverisolationexit+*mayonlyhappenoncetotransitionfromisolatedtonon-isolatedstate.+*Therefore,ifdecrementingthiscounterresultsinavaluelessthan0,+*isolationexitprocedurecan'tbestarted--italreadyhappened,oris+*inprogress,orisolationisnotenteredyet.+*/+DEFINE_PER_CPU(atomic_t,isol_counter);++/*+*DescriptionofthelasttwotasksthatranisolatedonagivenCPU.+*Thisisintendedonlyformessagesaboutisolationbreaking.We+*don'twantanyreferencestoactualtaskwhileaccessingthisfrom+*CPUthatcausedisolationbreaking--weknownothingabouttiming+*anddon'twanttouselockingorRCU.+*/+structisol_task_desc{+atomic_tcurr_index;+atomic_tcurr_index_wr;+boolwarned[2];+pid_tpid[2];+pid_ttgid[2];+charcomm[2][TASK_COMM_LEN];+};+staticDEFINE_PER_CPU(structisol_task_desc,isol_task_descs);++/*+*Counterforisolationexitingprocedures(fromrequesttothestartof+*cleanup)beingattemptedatonceonaCPU.Normallyincrementingof+*thiscounterisperformedfromtheCPUthatcausedisolationbreaking,+*howeverdecrementingisdonefromthecleanupprocedure,delegatedto+*theCPUthatisexitingisolation,notfromtheCPUthatcausedisolation+*breaking.+*+*Ifincrementingthiscounterwhilestartingisolationexitprocedure+*resultsinavaluegreaterthan0,isolationexitingisalreadyin+*progress,andcleanupdidnotstartyet.Thismeans,countershouldbe+*decrementedback,andisolationexitthatisalreadyinprogress,should+*beallowedtocomplete.Otherwise,anewisolationexitprocedureshould+*bestarted.+*/+DEFINE_PER_CPU(atomic_t,isol_exit_counter);++/*+*Descriptorforisolation-breakingSMPcalls+*/+DEFINE_PER_CPU(call_single_data_t,isol_break_csd);++cpumask_var_ttask_isolation_map;+cpumask_var_ttask_isolation_cleanup_map;+staticDEFINE_SPINLOCK(task_isolation_cleanup_lock);++/* We can run on cpus that are isolated from the scheduler and are nohz_full. */+staticint__inittask_isolation_init(void)+{+alloc_bootmem_cpumask_var(&task_isolation_cleanup_map);+if(alloc_cpumask_var(&task_isolation_map,GFP_KERNEL))+/*+*Atthispointtaskisolationshouldmatch+*nohz_full.Thismaychangeinthefuture.+*/+cpumask_copy(task_isolation_map,tick_nohz_full_mask);+return0;+}+core_initcall(task_isolation_init)++/* Enable stack backtraces of any interrupts of task_isolation cores. */+staticbooltask_isolation_debug;+staticint__inittask_isolation_debug_func(char*str)+{+task_isolation_debug=true;+return1;+}+__setup("task_isolation_debug",task_isolation_debug_func);++/*+*Recordname,pidandgrouppidofthetaskenteringisolationon+*thecurrentCPU.+*/+staticvoidrecord_curr_isolated_task(void)+{+intind;+intcpu=smp_processor_id();+structisol_task_desc*desc=&per_cpu(isol_task_descs,cpu);+structtask_struct*task=current;++/* Finish everything before recording current task */+smp_mb();+ind=atomic_inc_return(&desc->curr_index_wr)&1;+desc->comm[ind][sizeof(task->comm)-1]='\0';+memcpy(desc->comm[ind],task->comm,sizeof(task->comm)-1);+desc->pid[ind]=task->pid;+desc->tgid[ind]=task->tgid;+desc->warned[ind]=false;+/* Write everything, to be seen by other CPUs */+smp_mb();+atomic_inc(&desc->curr_index);+/* Everyone will see the new record from this point */+smp_mb();+}++/*+*Printmessageprefixedwiththedescriptionofthecurrent(or+*last)isolatedtaskonagivenCPU.Intendedforisolationbreaking+*messagesthatincludetargettaskfortheuser'sconvenience.+*+*Messagesproducedwiththisfunctionmayhaveobsoletetask+*informationifisolatedtasksmanagedtoexit,startandenter+*isolationmultipletimes,ormultipletaskstriedtoenter+*isolationonthesameCPUatonce.Forthoseunusualcasesitwould+*containavaliddescriptionofthecauseforisolationbreakingand+*targetCPUnumber,justnotthecorrectdescriptionofwhichtask+*endeduplosingisolation.+*/+inttask_isolation_message(intcpu,intlevel,boolsupp,constchar*fmt,...)+{+structisol_task_desc*desc;+structtask_struct*task;+va_listargs;+charbuf_prefix[TASK_COMM_LEN+20+3*20];+charbuf[200];+intcurr_cpu,ind_counter,ind_counter_old,ind;++curr_cpu=get_cpu();+desc=&per_cpu(isol_task_descs,cpu);+ind_counter=atomic_read(&desc->curr_index);++if(curr_cpu==cpu){+/*+*MessageisforthecurrentCPUsocurrent+*task_structshouldbeusedinsteadofcached+*information.+*+*Likeinotherdiagnosticmessages,ifissuedfrom+*interruptcontext,currentwillbetheinterrupted+*task.Unlikeotherdiagnosticmessages,thisis+*alwaysrelevantbecausethemessageisabout+*interruptingatask.+*/+ind=ind_counter&1;+if(supp&&desc->warned[ind]){+/*+*Ifsuppistrue,skipthemessageifthe+*sametaskwasmentionedinthemessage+*originatedonremoteCPU,anditdidnot+*re-enterisolatedstatesincethen(warned+*istrue).Onlylocalmessagesfollowing+*remotemessages,likelyaboutthesame+*isolationbreakingevent,areskippedto+*avoidduplication.Ifremotecauseis+*immediatelyfollowedbyalocalonebefore+*isolationisbroken,localcauseisskipped+*frommessages.+*/+put_cpu();+return0;+}+task=current;+snprintf(buf_prefix,sizeof(buf_prefix),+"isolation %s/%d/%d (cpu %d)",+task->comm,task->tgid,task->pid,cpu);+put_cpu();+}else{+/*+*MessageisforremoteCPU,usecachedinformation.+*/+put_cpu();+/*+*Makesure,indexremainedunchangedwhiledatawas+*copied.Ifitchanged,datathatwascopiedmaybe+*inconsistentbecausetwoupdatesinasequencecould+*overwritethedatawhileitwasbeingread.+*/+do{+/* Make sure we are reading up to date values */+smp_mb();+ind=ind_counter&1;+snprintf(buf_prefix,sizeof(buf_prefix),+"isolation %s/%d/%d (cpu %d)",+desc->comm[ind],desc->tgid[ind],+desc->pid[ind],cpu);+desc->warned[ind]=true;+ind_counter_old=ind_counter;+/* Record the warned flag, then re-read descriptor */+smp_mb();+ind_counter=atomic_read(&desc->curr_index);+/*+*Ifthecounterchanged,somethingwasupdated,so+*repeateverythingtogetthecurrentdata+*/+}while(ind_counter!=ind_counter_old);+}++va_start(args,fmt);+vsnprintf(buf,sizeof(buf),fmt,args);+va_end(args);++switch(level){+caseLOGLEVEL_EMERG:+pr_emerg("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_ALERT:+pr_alert("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_CRIT:+pr_crit("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_ERR:+pr_err("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_WARNING:+pr_warn("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_NOTICE:+pr_notice("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_INFO:+pr_info("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_DEBUG:+pr_debug("%s: %s",buf_prefix,buf);+break;+default:+/* No message without a valid level */+return0;+}+return1;+}++/*+*Dumpstackifneedbe.Thiscanbehelpfulevenfromthefinalexit+*tousermodecodesincestacktracessometimescarryinformationabout+*whatputyouintothekernel,e.g.aninterruptnumberencodedin+*theinitialentrystackframethatisstillvisibleatexittime.+*/+staticvoiddebug_dump_stack(void)+{+if(task_isolation_debug)+dump_stack();+}++/*+*Settheflagswordbutdon'ttrytoactuallystarttaskisolationyet.+*Wewillstartitwhenenteringuserspaceintask_isolation_start().+*/+inttask_isolation_request(unsignedintflags)+{+structtask_struct*task=current;++/*+*Thetaskisolationflagsshouldalwaysbeclearedjustby+*virtueofhavingenteredthekernel.+*/+WARN_ON_ONCE(test_tsk_thread_flag(task,TIF_TASK_ISOLATION));+WARN_ON_ONCE(task->task_isolation_flags!=0);+WARN_ON_ONCE(task->task_isolation_state!=STATE_NORMAL);++task->task_isolation_flags=flags;+if(!(task->task_isolation_flags&PR_TASK_ISOLATION_ENABLE))+return0;++/* We are trying to enable task isolation. */+set_tsk_thread_flag(task,TIF_TASK_ISOLATION);++/*+*Shutdownthevmstatworkersowe'renotinterruptedlater.+*Wehavetotrytodothishere(withinterruptsenabled)since+*wearecancelingdelayedworkandwillcallflush_work()+*(whichenablesinterrupts)andpossiblyschedule().+*/+quiet_vmstat_sync();++/* We return 0 here but we may change that in task_isolation_start(). */+return0;+}++/*+*Performactionsthatshouldbedoneimmediatelyonexitfromisolation.+*/+staticvoidfast_task_isolation_cpu_cleanup(void*info)+{+atomic_dec(&per_cpu(isol_exit_counter,smp_processor_id()));+/* At this point breaking isolation from other CPUs is possible again */++/*+*Thistaskisnolongerisolated(andifbyanychancethis+*isthewrongtask,it'salreadynotisolated)+*/+current->task_isolation_flags=0;+clear_tsk_thread_flag(current,TIF_TASK_ISOLATION);++/* Run the rest of cleanup later */+set_tsk_thread_flag(current,TIF_NOTIFY_RESUME);++/* Copy flags with task isolation disabled */+this_cpu_write(tsk_thread_flags_cache,+READ_ONCE(task_thread_info(current)->flags));+}++/* Disable task isolation for the specified task. */+staticvoidstop_isolation(structtask_struct*p)+{+intcpu,this_cpu;+unsignedlongflags;++this_cpu=get_cpu();+cpu=task_cpu(p);+if(atomic_inc_return(&per_cpu(isol_exit_counter,cpu))>1){+/* Already exiting isolation */+atomic_dec(&per_cpu(isol_exit_counter,cpu));+put_cpu();+return;+}++if(p==current){+p->task_isolation_state=STATE_NORMAL;+fast_task_isolation_cpu_cleanup(NULL);+task_isolation_cpu_cleanup();+if(atomic_dec_return(&per_cpu(isol_counter,cpu))<0){+/* Is not isolated already */+atomic_inc(&per_cpu(isol_counter,cpu));+}+put_cpu();+}else{+if(atomic_dec_return(&per_cpu(isol_counter,cpu))<0){+/* Is not isolated already */+atomic_inc(&per_cpu(isol_counter,cpu));+atomic_dec(&per_cpu(isol_exit_counter,cpu));+put_cpu();+return;+}+/*+*Schedule"slow"cleanup.Thisrelieson+*TIF_NOTIFY_RESUMEbeingset+*/+spin_lock_irqsave(&task_isolation_cleanup_lock,flags);+cpumask_set_cpu(cpu,task_isolation_cleanup_map);+spin_unlock_irqrestore(&task_isolation_cleanup_lock,flags);+/*+*SettingflagsisdelegatedtotheCPUwhere+*isolatedtaskisrunning+*isol_exit_counterwillbedecrementedfromthereaswell.+*/+per_cpu(isol_break_csd,cpu).func=+fast_task_isolation_cpu_cleanup;+per_cpu(isol_break_csd,cpu).info=NULL;+per_cpu(isol_break_csd,cpu).flags=0;+smp_call_function_single_async(cpu,+&per_cpu(isol_break_csd,cpu));+put_cpu();+}+}++/*+*Thiscoderunswithinterruptsdisabledjustbeforethereturnto+*userspace,afteraprctl()hasrequestedenablingtaskisolation.+*Wetakewhateverstepsareneededtoavoidbeinginterruptedlater:+*drainthelrupages,stoptheschedulertick,etc.More+*functionalitymaybeaddedherelatertoavoidothertypesof+*interruptsfromotherkernelsubsystems.+*+*Ifwecan'tenabletaskisolation,weupdatethesyscallreturn+*valuewithanappropriateerror.+*/+voidtask_isolation_start(void)+{+interror;++/*+*WeshouldonlybecalledinSTATE_NORMAL(isolationdisabled),+*onourwayoutofthekernelfromtheprctl()thatturnediton.+*Ifweareexitingfromthekernelinanotherstate,itmeanswe+*madeitbackintothekernelwithoutdisablingtaskisolation,+*andweshouldinvestigatehow(andinanycasedisabletask+*isolationatthispoint).Weareclearlynotonthepathback+*fromtheprctl()sowedon'ttouchthesyscallreturnvalue.+*/+if(WARN_ON_ONCE(current->task_isolation_state!=STATE_NORMAL)){+/* Increment counter, this will allow isolation breaking */+if(atomic_inc_return(&per_cpu(isol_counter,+smp_processor_id()))>1){+atomic_dec(&per_cpu(isol_counter,smp_processor_id()));+}+atomic_inc(&per_cpu(isol_counter,smp_processor_id()));+stop_isolation(current);+return;+}++/*+*Mustbeaffinitizedtoasinglecorewithtaskisolationpossible.+*Inprinciplethiscouldberemotelymodifiedbetweentheprctl()+*andthereturntouserspace,sowehavetocheckithere.+*/+if(current->nr_cpus_allowed!=1||+!is_isolation_cpu(smp_processor_id())){+error=-EINVAL;+gotoerror;+}++/* If the vmstat delayed work is not canceled, we have to try again. */+if(!vmstat_idle()){+error=-EAGAIN;+gotoerror;+}++/* Try to stop the dynamic tick. */+error=try_stop_full_tick();+if(error)+gotoerror;++/* Drain the pagevecs to avoid unnecessary IPI flushes later. */+lru_add_drain();++/* Increment counter, this will allow isolation breaking */+if(atomic_inc_return(&per_cpu(isol_counter,+smp_processor_id()))>1){+atomic_dec(&per_cpu(isol_counter,smp_processor_id()));+}++/* Record isolated task IDs and name */+record_curr_isolated_task();++/* Copy flags with task isolation enabled */+this_cpu_write(tsk_thread_flags_cache,+READ_ONCE(task_thread_info(current)->flags));++current->task_isolation_state=STATE_ISOLATED;+return;++error:+/* Increment counter, this will allow isolation breaking */+if(atomic_inc_return(&per_cpu(isol_counter,+smp_processor_id()))>1){+atomic_dec(&per_cpu(isol_counter,smp_processor_id()));+}+stop_isolation(current);+syscall_set_return_value(current,current_pt_regs(),error,0);+}++/* Stop task isolation on the remote task and send it a signal. */+staticvoidsend_isolation_signal(structtask_struct*task)+{+intflags=task->task_isolation_flags;+kernel_siginfo_tinfo={+.si_signo=PR_TASK_ISOLATION_GET_SIG(flags)?:SIGKILL,+};++stop_isolation(task);+send_sig_info(info.si_signo,&info,task);+}++/* Only a few syscalls are valid once we are in task isolation mode. */+staticboolis_acceptable_syscall(intsyscall)+{+/* No need to incur an isolation signal if we are just exiting. */+if(syscall==__NR_exit||syscall==__NR_exit_group)+returntrue;++/* Check to see if it's the prctl for isolation. */+if(syscall==__NR_prctl){+unsignedlongarg[SYSCALL_MAX_ARGS];++syscall_get_arguments(current,current_pt_regs(),arg);+if(arg[0]==PR_TASK_ISOLATION)+returntrue;+}++returnfalse;+}++/*+*Thisroutineiscalledfromsyscallentry,preventsmostsyscalls+*fromexecuting,andifneededraisesasignaltonotifytheprocess.+*+*Notethatwehavetostopisolationbeforeweevenprintamessage+*here,sinceotherwisewemightendupreportinganinterruptdueto+*kickingtheprintkhandlingcode,ratherthanreportingthetrue+*causeofinterrupthere.+*+*Themessageisnotsuppressedbypreviousremotelytriggered+*messages.+*/+inttask_isolation_syscall(intsyscall)+{+structtask_struct*task=current;++if(is_acceptable_syscall(syscall)){+stop_isolation(task);+return0;+}++send_isolation_signal(task);++pr_task_isol_warn(smp_processor_id(),+"task_isolation lost due to syscall %d\n",+syscall);+debug_dump_stack();++syscall_set_return_value(task,current_pt_regs(),-ERESTARTNOINTR,-1);+return-1;+}++/*+*Thisroutineiscalledfromanyexceptionorirqthatdoesn't+*otherwisetriggerasignaltotheuserprocess(e.g.pagefault).+*+*Messageswillbesuppressedifthereisalreadyareportedremote+*causeforisolationbreaking,sowedon'tgeneratemultiple+*confusinglysimilarmessagesaboutthesameevent.+*/+void_task_isolation_interrupt(constchar*fmt,...)+{+structtask_struct*task=current;+va_listargs;+charbuf[100];++/* RCU should have been enabled prior to this point. */+RCU_LOCKDEP_WARN(!rcu_is_watching(),"kernel entry without RCU");++/* Are we exiting isolation already? */+if(atomic_read(&per_cpu(isol_exit_counter,smp_processor_id()))!=0){+task->task_isolation_state=STATE_NORMAL;+return;+}+/*+*Avoidreportinginterruptsthathappenafterwehaveprctl'ed+*toenableisolation,butbeforewehavereturnedtouserspace.+*/+if(task->task_isolation_state==STATE_NORMAL)+return;++va_start(args,fmt);+vsnprintf(buf,sizeof(buf),fmt,args);+va_end(args);++/* Handle NMIs minimally, since we can't send a signal. */+if(in_nmi()){+pr_task_isol_err(smp_processor_id(),+"isolation: in NMI; not delivering signal\n");+}else{+send_isolation_signal(task);+}++if(pr_task_isol_warn_supp(smp_processor_id(),+"task_isolation lost due to %s\n",buf))+debug_dump_stack();+}++/*+*Calledbeforewewakeupataskthathasasignaltoprocess.+*Needstobedonetohandleinterruptsthattriggersignals,which+*wedon'tcatchwithtask_isolation_interrupt()hooks.+*+*Thismessageisalsosuppressediftherewasalreadyaremotely+*causedmessageaboutthesameisolationbreakingevent.+*/+void_task_isolation_signal(structtask_struct*task)+{+structisol_task_desc*desc;+intind,cpu;+booldo_warn=(task->task_isolation_state==STATE_ISOLATED);++cpu=task_cpu(task);+desc=&per_cpu(isol_task_descs,cpu);+ind=atomic_read(&desc->curr_index)&1;+if(desc->warned[ind])+do_warn=false;++stop_isolation(task);++if(do_warn){+pr_warn("isolation: %s/%d/%d (cpu %d): task_isolation lost due to signal\n",+task->comm,task->tgid,task->pid,cpu);+debug_dump_stack();+}+}++/*+*Generateastackbacktraceifwearegoingtointerruptanothertask+*isolationprocess.+*/+voidtask_isolation_remote(intcpu,constchar*fmt,...)+{+structtask_struct*curr_task;+va_listargs;+charbuf[200];++if(!is_isolation_cpu(cpu)||!task_isolation_on_cpu(cpu))+return;++curr_task=current;++va_start(args,fmt);+vsnprintf(buf,sizeof(buf),fmt,args);+va_end(args);+if(pr_task_isol_warn(cpu,+"task_isolation lost due to %s by %s/%d/%d on cpu %d\n",+buf,+curr_task->comm,curr_task->tgid,+curr_task->pid,smp_processor_id()))+debug_dump_stack();+}++/*+*Generateastackbacktraceifanyofthecpusin"mask"arerunning+*taskisolationprocesses.+*/+voidtask_isolation_remote_cpumask(conststructcpumask*mask,+constchar*fmt,...)+{+structtask_struct*curr_task;+cpumask_var_twarn_mask;+va_listargs;+charbuf[200];+intcpu,first_cpu;++if(task_isolation_map==NULL||+!zalloc_cpumask_var(&warn_mask,GFP_KERNEL))+return;++first_cpu=-1;+for_each_cpu_and(cpu,mask,task_isolation_map){+if(task_isolation_on_cpu(cpu)){+if(first_cpu<0)+first_cpu=cpu;+else+cpumask_set_cpu(cpu,warn_mask);+}+}++if(first_cpu<0)+gotodone;++curr_task=current;++va_start(args,fmt);+vsnprintf(buf,sizeof(buf),fmt,args);+va_end(args);++if(cpumask_weight(warn_mask)==0)+pr_task_isol_warn(first_cpu,+"task_isolation lost due to %s by %s/%d/%d on cpu %d\n",+buf,curr_task->comm,curr_task->tgid,+curr_task->pid,smp_processor_id());+else+pr_task_isol_warn(first_cpu,+" and cpus %*pbl: task_isolation lost due to %s by %s/%d/%d on cpu %d\n",+cpumask_pr_args(warn_mask),+buf,curr_task->comm,curr_task->tgid,+curr_task->pid,smp_processor_id());+debug_dump_stack();++done:+free_cpumask_var(warn_mask);+}++/*+*CheckifgivenCPUisrunningisolatedtask.+*/+inttask_isolation_on_cpu(intcpu)+{+returntest_bit(TIF_TASK_ISOLATION,+&per_cpu(tsk_thread_flags_cache,cpu));+}++/*+*SetCPUscurrentlyrunningisolatedtasksinCPUmask.+*/+voidtask_isolation_cpumask(structcpumask*mask)+{+intcpu;++if(task_isolation_map==NULL)+return;++for_each_cpu(cpu,task_isolation_map)+if(task_isolation_on_cpu(cpu))+cpumask_set_cpu(cpu,mask);+}++/*+*ClearCPUscurrentlyrunningisolatedtasksinCPUmask.+*/+voidtask_isolation_clear_cpumask(structcpumask*mask)+{+intcpu;++if(task_isolation_map==NULL)+return;++for_each_cpu(cpu,task_isolation_map)+if(task_isolation_on_cpu(cpu))+cpumask_clear_cpu(cpu,mask);+}++/*+*Cleanupprocedure.Thecalltothisproceduremaybedelayed.+*/+voidtask_isolation_cpu_cleanup(void)+{+kick_hrtimer();+}++/*+*CheckifcleanupisscheduledonthecurrentCPU,andifso,runit.+*Intendedtobecalledfromnotify_resume()oranothersuchcallback+*onthetargetCPU.+*/+voidtask_isolation_check_run_cleanup(void)+{+intcpu;+unsignedlongflags;++spin_lock_irqsave(&task_isolation_cleanup_lock,flags);++cpu=smp_processor_id();++if(cpumask_test_cpu(cpu,task_isolation_cleanup_map)){+cpumask_clear_cpu(cpu,task_isolation_cleanup_map);+spin_unlock_irqrestore(&task_isolation_cleanup_lock,flags);+task_isolation_cpu_cleanup();+}else+spin_unlock_irqrestore(&task_isolation_cleanup_lock,flags);+}
@@ -882,6 +882,24 @@ static void tick_nohz_full_update_tick(struct tick_sched *ts)#endif}+#ifdef CONFIG_TASK_ISOLATION+inttry_stop_full_tick(void)+{+intcpu=smp_processor_id();+structtick_sched*ts=this_cpu_ptr(&tick_cpu_sched);++/* For an unstable clock, we should return a permanent error code. */+if(atomic_read(&tick_dep_mask)&TICK_DEP_MASK_CLOCK_UNSTABLE)+return-EINVAL;++if(!can_stop_full_tick(cpu,ts))+return-EAGAIN;++tick_nohz_stop_sched_tick(ts,cpu);+return0;+}+#endif+staticboolcan_stop_idle_tick(intcpu,structtick_sched*ts){/*
On Sun, Mar 08, 2020 at 03:47:08AM +0000, Alex Belits wrote:
quoted hunk
The existing nohz_full mode is designed as a "soft" isolation mode
that makes tradeoffs to minimize userspace interruptions while
still attempting to avoid overheads in the kernel entry/exit path,
to provide 100% kernel semantics, etc.
However, some applications require a "hard" commitment from the
kernel to avoid interruptions, in particular userspace device driver
style applications, such as high-speed networking code.
This change introduces a framework to allow applications
to elect to have the "hard" semantics as needed, specifying
prctl(PR_TASK_ISOLATION, PR_TASK_ISOLATION_ENABLE) to do so.
The kernel must be built with the new TASK_ISOLATION Kconfig flag
to enable this mode, and the kernel booted with an appropriate
"isolcpus=nohz,domain,CPULIST" boot argument to enable
nohz_full and isolcpus. The "task_isolation" state is then indicated
by setting a new task struct field, task_isolation_flag, to the
value passed by prctl(), and also setting a TIF_TASK_ISOLATION
bit in the thread_info flags. When the kernel is returning to
userspace from the prctl() call and sees TIF_TASK_ISOLATION set,
it calls the new task_isolation_start() routine to arrange for
the task to avoid being interrupted in the future.
With interrupts disabled, task_isolation_start() ensures that kernel
subsystems that might cause a future interrupt are quiesced. If it
doesn't succeed, it adjusts the syscall return value to indicate that
fact, and userspace can retry as desired. In addition to stopping
the scheduler tick, the code takes any actions that might avoid
a future interrupt to the core, such as a worker thread being
scheduled that could be quiesced now (e.g. the vmstat worker)
or a future IPI to the core to clean up some state that could be
cleaned up now (e.g. the mm lru per-cpu cache).
Once the task has returned to userspace after issuing the prctl(),
if it enters the kernel again via system call, page fault, or any
other exception or irq, the kernel will kill it with SIGKILL.
In addition to sending a signal, the code supports a kernel
command-line "task_isolation_debug" flag which causes a stack
backtrace to be generated whenever a task loses isolation.
To allow the state to be entered and exited, the syscall checking
test ignores the prctl(PR_TASK_ISOLATION) syscall so that we can
clear the bit again later, and ignores exit/exit_group to allow
exiting the task without a pointless signal being delivered.
The prctl() API allows for specifying a signal number to use instead
of the default SIGKILL, to allow for catching the notification
signal; for example, in a production environment, it might be
helpful to log information to the application logging mechanism
before exiting. Or, the signal handler might choose to reset the
program counter back to the code segment intended to be run isolated
via prctl() to continue execution.
In a number of cases we can tell on a remote cpu that we are
going to be interrupting the cpu, e.g. via an IPI or a TLB flush.
In that case we generate the diagnostic (and optional stack dump)
on the remote core to be able to deliver better diagnostics.
If the interrupt is not something caught by Linux (e.g. a
hypervisor interrupt) we can also request a reschedule IPI to
be sent to the remote core so it can be sure to generate a
signal to notify the process.
Separate patches that follow provide these changes for x86, arm,
and arm64.
Signed-off-by: Alex Belits <redacted>
---
.../admin-guide/kernel-parameters.txt | 6 +
include/linux/hrtimer.h | 4 +
include/linux/isolation.h | 229 ++++++
include/linux/sched.h | 4 +
include/linux/tick.h | 3 +
include/uapi/linux/prctl.h | 6 +
init/Kconfig | 28 +
kernel/Makefile | 2 +
kernel/context_tracking.c | 2 +
kernel/isolation.c | 774 ++++++++++++++++++
kernel/signal.c | 2 +
kernel/sys.c | 6 +
kernel/time/hrtimer.c | 27 +
kernel/time/tick-sched.c | 18 +
14 files changed, 1111 insertions(+)
create mode 100644 include/linux/isolation.h
create mode 100644 kernel/isolation.c
@@ -4808,6 +4808,12 @@ neutralize any effect of /proc/sys/kernel/sysrq. Useful for debugging.+ task_isolation_debug [KNL]+ In kernels built with CONFIG_TASK_ISOLATION, this+ setting will generate console backtraces to+ accompany the diagnostics generated about+ interrupting tasks running with task isolation.+ tcpmhash_entries= [KNL,NET] Set the number of tcp_metrics_hash slots. Default value is 8192 or 16384 depending on total
@@ -576,6 +576,34 @@ config CPU_ISOLATIONsource"kernel/rcu/Kconfig"+configHAVE_ARCH_TASK_ISOLATION+bool++configTASK_ISOLATION+bool"Provide hard CPU isolation from the kernel on demand"+depends onNO_HZ_FULL&&HAVE_ARCH_TASK_ISOLATION+help++Allowuserspaceprocessesthatplacethemselvesoncoreswith+nohz_fullandisolcpusenabled,andrunprctl(PR_TASK_ISOLATION),+to"isolate"themselvesfromthekernel.Priortoreturningto+userspace,isolatedtaskswillarrangethatnofuturekernel+activitywillinterruptthetaskwhilethetaskisrunningin+userspace.Attemptingtore-enterthekernelwhileinthismode+willcausethetasktobeterminatedwithasignal;youmust+explicitlyuseprctl()todisabletaskisolationbeforeresuming+normaluseofthekernel.++This"hard"isolationfromthekernelisrequiredforuserspace+tasksthatarerunninghardreal-timetasksinuserspace,suchas+ahigh-speednetworkdriverinuserspace.Withoutthisoption,but+withNO_HZ_FULLenabled,thekernelwillmakeabest-faith,"soft"+efforttoshieldasingleuserspaceprocessfrominterrupts,but+makesnoguarantees.++Youshouldsay"N"unlessyouareintendingtoruna+high-performanceuserspacedriverorsimilartask.+configBUILD_BIN2Cbooldefaultn
@@ -0,0 +1,774 @@+// SPDX-License-Identifier: GPL-2.0-only+/*+*linux/kernel/isolation.c+*+*Implementationoftaskisolation.+*+*Authors:+*ChrisMetcalf<cmetcalf@mellanox.com>+*AlexBelits<abelits@marvell.com>+*YuriNorov<ynorov@marvell.com>+*/++#include<linux/mm.h>+#include<linux/swap.h>+#include<linux/vmstat.h>+#include<linux/sched.h>+#include<linux/isolation.h>+#include<linux/syscalls.h>+#include<linux/smp.h>+#include<linux/tick.h>+#include<asm/unistd.h>+#include<asm/syscall.h>+#include<linux/hrtimer.h>++/*+*Thesevaluesarestoredintask_isolation_state.+*NotethatSTATE_NORMAL+TIF_TASK_ISOLATIONmeanswearestill+*returningfromsys_prctl()touserspace.+*/+enum{+STATE_NORMAL=0,/* Not isolated */+STATE_ISOLATED=1/* In userspace, isolated */+};++/*+*Thisvariablecontainsthreadflagscopiedatthemoment+*whenschedule()switchedtothetaskonagivenCPU,+*or0ifnotaskisrunning.+*/+DEFINE_PER_CPU(unsignedlong,tsk_thread_flags_cache);++/*+*CounterforisolationstateonagivenCPU,incrementswhenentering+*isolationanddecrementswhenexitingisolation(beforeorafterthe+*cleanup).Multiplesimultaneouslyrunningproceduresenteringor+*exitingisolationarepreventedbycheckingtheresultof+*incrementingordecrementingthisvariable.Thisvariableisboth+*incrementedanddecrementedbyCPUthatcausedisolationenteringor+*exit.+*+*Thisisnecessarybecausemultipleisolation-breakingeventsmayhappen+*atonce(oroneastheresultoftheother),howeverisolationexit+*mayonlyhappenoncetotransitionfromisolatedtonon-isolatedstate.+*Therefore,ifdecrementingthiscounterresultsinavaluelessthan0,+*isolationexitprocedurecan'tbestarted--italreadyhappened,oris+*inprogress,orisolationisnotenteredyet.+*/+DEFINE_PER_CPU(atomic_t,isol_counter);++/*+*DescriptionofthelasttwotasksthatranisolatedonagivenCPU.+*Thisisintendedonlyformessagesaboutisolationbreaking.We+*don'twantanyreferencestoactualtaskwhileaccessingthisfrom+*CPUthatcausedisolationbreaking--weknownothingabouttiming+*anddon'twanttouselockingorRCU.+*/+structisol_task_desc{+atomic_tcurr_index;+atomic_tcurr_index_wr;+boolwarned[2];+pid_tpid[2];+pid_ttgid[2];+charcomm[2][TASK_COMM_LEN];+};+staticDEFINE_PER_CPU(structisol_task_desc,isol_task_descs);++/*+*Counterforisolationexitingprocedures(fromrequesttothestartof+*cleanup)beingattemptedatonceonaCPU.Normallyincrementingof+*thiscounterisperformedfromtheCPUthatcausedisolationbreaking,+*howeverdecrementingisdonefromthecleanupprocedure,delegatedto+*theCPUthatisexitingisolation,notfromtheCPUthatcausedisolation+*breaking.+*+*Ifincrementingthiscounterwhilestartingisolationexitprocedure+*resultsinavaluegreaterthan0,isolationexitingisalreadyin+*progress,andcleanupdidnotstartyet.Thismeans,countershouldbe+*decrementedback,andisolationexitthatisalreadyinprogress,should+*beallowedtocomplete.Otherwise,anewisolationexitprocedureshould+*bestarted.+*/+DEFINE_PER_CPU(atomic_t,isol_exit_counter);++/*+*Descriptorforisolation-breakingSMPcalls+*/+DEFINE_PER_CPU(call_single_data_t,isol_break_csd);++cpumask_var_ttask_isolation_map;+cpumask_var_ttask_isolation_cleanup_map;+staticDEFINE_SPINLOCK(task_isolation_cleanup_lock);++/* We can run on cpus that are isolated from the scheduler and are nohz_full. */+staticint__inittask_isolation_init(void)+{+alloc_bootmem_cpumask_var(&task_isolation_cleanup_map);+if(alloc_cpumask_var(&task_isolation_map,GFP_KERNEL))+/*+*Atthispointtaskisolationshouldmatch+*nohz_full.Thismaychangeinthefuture.+*/+cpumask_copy(task_isolation_map,tick_nohz_full_mask);+return0;+}+core_initcall(task_isolation_init)++/* Enable stack backtraces of any interrupts of task_isolation cores. */+staticbooltask_isolation_debug;+staticint__inittask_isolation_debug_func(char*str)+{+task_isolation_debug=true;+return1;+}+__setup("task_isolation_debug",task_isolation_debug_func);++/*+*Recordname,pidandgrouppidofthetaskenteringisolationon+*thecurrentCPU.+*/+staticvoidrecord_curr_isolated_task(void)+{+intind;+intcpu=smp_processor_id();+structisol_task_desc*desc=&per_cpu(isol_task_descs,cpu);+structtask_struct*task=current;++/* Finish everything before recording current task */+smp_mb();+ind=atomic_inc_return(&desc->curr_index_wr)&1;+desc->comm[ind][sizeof(task->comm)-1]='\0';+memcpy(desc->comm[ind],task->comm,sizeof(task->comm)-1);+desc->pid[ind]=task->pid;+desc->tgid[ind]=task->tgid;+desc->warned[ind]=false;+/* Write everything, to be seen by other CPUs */+smp_mb();+atomic_inc(&desc->curr_index);+/* Everyone will see the new record from this point */+smp_mb();+}++/*+*Printmessageprefixedwiththedescriptionofthecurrent(or+*last)isolatedtaskonagivenCPU.Intendedforisolationbreaking+*messagesthatincludetargettaskfortheuser'sconvenience.+*+*Messagesproducedwiththisfunctionmayhaveobsoletetask+*informationifisolatedtasksmanagedtoexit,startandenter+*isolationmultipletimes,ormultipletaskstriedtoenter+*isolationonthesameCPUatonce.Forthoseunusualcasesitwould+*containavaliddescriptionofthecauseforisolationbreakingand+*targetCPUnumber,justnotthecorrectdescriptionofwhichtask+*endeduplosingisolation.+*/+inttask_isolation_message(intcpu,intlevel,boolsupp,constchar*fmt,...)+{+structisol_task_desc*desc;+structtask_struct*task;+va_listargs;+charbuf_prefix[TASK_COMM_LEN+20+3*20];+charbuf[200];+intcurr_cpu,ind_counter,ind_counter_old,ind;++curr_cpu=get_cpu();+desc=&per_cpu(isol_task_descs,cpu);+ind_counter=atomic_read(&desc->curr_index);++if(curr_cpu==cpu){+/*+*MessageisforthecurrentCPUsocurrent+*task_structshouldbeusedinsteadofcached+*information.+*+*Likeinotherdiagnosticmessages,ifissuedfrom+*interruptcontext,currentwillbetheinterrupted+*task.Unlikeotherdiagnosticmessages,thisis+*alwaysrelevantbecausethemessageisabout+*interruptingatask.+*/+ind=ind_counter&1;+if(supp&&desc->warned[ind]){+/*+*Ifsuppistrue,skipthemessageifthe+*sametaskwasmentionedinthemessage+*originatedonremoteCPU,anditdidnot+*re-enterisolatedstatesincethen(warned+*istrue).Onlylocalmessagesfollowing+*remotemessages,likelyaboutthesame+*isolationbreakingevent,areskippedto+*avoidduplication.Ifremotecauseis+*immediatelyfollowedbyalocalonebefore+*isolationisbroken,localcauseisskipped+*frommessages.+*/+put_cpu();+return0;+}+task=current;+snprintf(buf_prefix,sizeof(buf_prefix),+"isolation %s/%d/%d (cpu %d)",+task->comm,task->tgid,task->pid,cpu);+put_cpu();+}else{+/*+*MessageisforremoteCPU,usecachedinformation.+*/+put_cpu();+/*+*Makesure,indexremainedunchangedwhiledatawas+*copied.Ifitchanged,datathatwascopiedmaybe+*inconsistentbecausetwoupdatesinasequencecould+*overwritethedatawhileitwasbeingread.+*/+do{+/* Make sure we are reading up to date values */+smp_mb();+ind=ind_counter&1;+snprintf(buf_prefix,sizeof(buf_prefix),+"isolation %s/%d/%d (cpu %d)",+desc->comm[ind],desc->tgid[ind],+desc->pid[ind],cpu);+desc->warned[ind]=true;+ind_counter_old=ind_counter;+/* Record the warned flag, then re-read descriptor */+smp_mb();+ind_counter=atomic_read(&desc->curr_index);+/*+*Ifthecounterchanged,somethingwasupdated,so+*repeateverythingtogetthecurrentdata+*/+}while(ind_counter!=ind_counter_old);+}++va_start(args,fmt);+vsnprintf(buf,sizeof(buf),fmt,args);+va_end(args);++switch(level){+caseLOGLEVEL_EMERG:+pr_emerg("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_ALERT:+pr_alert("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_CRIT:+pr_crit("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_ERR:+pr_err("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_WARNING:+pr_warn("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_NOTICE:+pr_notice("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_INFO:+pr_info("%s: %s",buf_prefix,buf);+break;+caseLOGLEVEL_DEBUG:+pr_debug("%s: %s",buf_prefix,buf);+break;+default:+/* No message without a valid level */+return0;+}+return1;+}++/*+*Dumpstackifneedbe.Thiscanbehelpfulevenfromthefinalexit+*tousermodecodesincestacktracessometimescarryinformationabout+*whatputyouintothekernel,e.g.aninterruptnumberencodedin+*theinitialentrystackframethatisstillvisibleatexittime.+*/+staticvoiddebug_dump_stack(void)+{+if(task_isolation_debug)+dump_stack();+}++/*+*Settheflagswordbutdon'ttrytoactuallystarttaskisolationyet.+*Wewillstartitwhenenteringuserspaceintask_isolation_start().+*/+inttask_isolation_request(unsignedintflags)+{+structtask_struct*task=current;++/*+*Thetaskisolationflagsshouldalwaysbeclearedjustby+*virtueofhavingenteredthekernel.+*/+WARN_ON_ONCE(test_tsk_thread_flag(task,TIF_TASK_ISOLATION));+WARN_ON_ONCE(task->task_isolation_flags!=0);+WARN_ON_ONCE(task->task_isolation_state!=STATE_NORMAL);++task->task_isolation_flags=flags;+if(!(task->task_isolation_flags&PR_TASK_ISOLATION_ENABLE))+return0;++/* We are trying to enable task isolation. */+set_tsk_thread_flag(task,TIF_TASK_ISOLATION);++/*+*Shutdownthevmstatworkersowe'renotinterruptedlater.+*Wehavetotrytodothishere(withinterruptsenabled)since+*wearecancelingdelayedworkandwillcallflush_work()+*(whichenablesinterrupts)andpossiblyschedule().+*/+quiet_vmstat_sync();++/* We return 0 here but we may change that in task_isolation_start(). */+return0;+}++/*+*Performactionsthatshouldbedoneimmediatelyonexitfromisolation.+*/+staticvoidfast_task_isolation_cpu_cleanup(void*info)+{+atomic_dec(&per_cpu(isol_exit_counter,smp_processor_id()));+/* At this point breaking isolation from other CPUs is possible again */++/*+*Thistaskisnolongerisolated(andifbyanychancethis+*isthewrongtask,it'salreadynotisolated)+*/+current->task_isolation_flags=0;+clear_tsk_thread_flag(current,TIF_TASK_ISOLATION);++/* Run the rest of cleanup later */+set_tsk_thread_flag(current,TIF_NOTIFY_RESUME);++/* Copy flags with task isolation disabled */+this_cpu_write(tsk_thread_flags_cache,+READ_ONCE(task_thread_info(current)->flags));+}++/* Disable task isolation for the specified task. */+staticvoidstop_isolation(structtask_struct*p)+{+intcpu,this_cpu;+unsignedlongflags;++this_cpu=get_cpu();+cpu=task_cpu(p);+if(atomic_inc_return(&per_cpu(isol_exit_counter,cpu))>1){+/* Already exiting isolation */+atomic_dec(&per_cpu(isol_exit_counter,cpu));+put_cpu();+return;+}++if(p==current){+p->task_isolation_state=STATE_NORMAL;+fast_task_isolation_cpu_cleanup(NULL);+task_isolation_cpu_cleanup();+if(atomic_dec_return(&per_cpu(isol_counter,cpu))<0){+/* Is not isolated already */+atomic_inc(&per_cpu(isol_counter,cpu));+}+put_cpu();+}else{+if(atomic_dec_return(&per_cpu(isol_counter,cpu))<0){+/* Is not isolated already */+atomic_inc(&per_cpu(isol_counter,cpu));+atomic_dec(&per_cpu(isol_exit_counter,cpu));+put_cpu();+return;+}+/*+*Schedule"slow"cleanup.Thisrelieson+*TIF_NOTIFY_RESUMEbeingset+*/+spin_lock_irqsave(&task_isolation_cleanup_lock,flags);+cpumask_set_cpu(cpu,task_isolation_cleanup_map);+spin_unlock_irqrestore(&task_isolation_cleanup_lock,flags);+/*+*SettingflagsisdelegatedtotheCPUwhere+*isolatedtaskisrunning+*isol_exit_counterwillbedecrementedfromthereaswell.+*/+per_cpu(isol_break_csd,cpu).func=+fast_task_isolation_cpu_cleanup;+per_cpu(isol_break_csd,cpu).info=NULL;+per_cpu(isol_break_csd,cpu).flags=0;+smp_call_function_single_async(cpu,+&per_cpu(isol_break_csd,cpu));+put_cpu();+}+}++/*+*Thiscoderunswithinterruptsdisabledjustbeforethereturnto+*userspace,afteraprctl()hasrequestedenablingtaskisolation.+*Wetakewhateverstepsareneededtoavoidbeinginterruptedlater:+*drainthelrupages,stoptheschedulertick,etc.More+*functionalitymaybeaddedherelatertoavoidothertypesof+*interruptsfromotherkernelsubsystems.+*+*Ifwecan'tenabletaskisolation,weupdatethesyscallreturn+*valuewithanappropriateerror.+*/+voidtask_isolation_start(void)+{+interror;++/*+*WeshouldonlybecalledinSTATE_NORMAL(isolationdisabled),+*onourwayoutofthekernelfromtheprctl()thatturnediton.+*Ifweareexitingfromthekernelinanotherstate,itmeanswe+*madeitbackintothekernelwithoutdisablingtaskisolation,+*andweshouldinvestigatehow(andinanycasedisabletask+*isolationatthispoint).Weareclearlynotonthepathback+*fromtheprctl()sowedon'ttouchthesyscallreturnvalue.+*/+if(WARN_ON_ONCE(current->task_isolation_state!=STATE_NORMAL)){+/* Increment counter, this will allow isolation breaking */+if(atomic_inc_return(&per_cpu(isol_counter,+smp_processor_id()))>1){+atomic_dec(&per_cpu(isol_counter,smp_processor_id()));+}+atomic_inc(&per_cpu(isol_counter,smp_processor_id()));+stop_isolation(current);+return;+}++/*+*Mustbeaffinitizedtoasinglecorewithtaskisolationpossible.+*Inprinciplethiscouldberemotelymodifiedbetweentheprctl()+*andthereturntouserspace,sowehavetocheckithere.+*/+if(current->nr_cpus_allowed!=1||+!is_isolation_cpu(smp_processor_id())){+error=-EINVAL;+gotoerror;+}++/* If the vmstat delayed work is not canceled, we have to try again. */+if(!vmstat_idle()){+error=-EAGAIN;+gotoerror;+}++/* Try to stop the dynamic tick. */+error=try_stop_full_tick();+if(error)+gotoerror;++/* Drain the pagevecs to avoid unnecessary IPI flushes later. */+lru_add_drain();++/* Increment counter, this will allow isolation breaking */+if(atomic_inc_return(&per_cpu(isol_counter,+smp_processor_id()))>1){+atomic_dec(&per_cpu(isol_counter,smp_processor_id()));+}++/* Record isolated task IDs and name */+record_curr_isolated_task();++/* Copy flags with task isolation enabled */+this_cpu_write(tsk_thread_flags_cache,+READ_ONCE(task_thread_info(current)->flags));++current->task_isolation_state=STATE_ISOLATED;+return;++error:+/* Increment counter, this will allow isolation breaking */+if(atomic_inc_return(&per_cpu(isol_counter,+smp_processor_id()))>1){+atomic_dec(&per_cpu(isol_counter,smp_processor_id()));+}+stop_isolation(current);+syscall_set_return_value(current,current_pt_regs(),error,0);+}++/* Stop task isolation on the remote task and send it a signal. */+staticvoidsend_isolation_signal(structtask_struct*task)+{+intflags=task->task_isolation_flags;+kernel_siginfo_tinfo={+.si_signo=PR_TASK_ISOLATION_GET_SIG(flags)?:SIGKILL,+};++stop_isolation(task);+send_sig_info(info.si_signo,&info,task);+}++/* Only a few syscalls are valid once we are in task isolation mode. */+staticboolis_acceptable_syscall(intsyscall)+{+/* No need to incur an isolation signal if we are just exiting. */+if(syscall==__NR_exit||syscall==__NR_exit_group)+returntrue;++/* Check to see if it's the prctl for isolation. */+if(syscall==__NR_prctl){+unsignedlongarg[SYSCALL_MAX_ARGS];
gcc output:
kernel/isolation.c: In function 'is_acceptable_syscall':
kernel/isolation.c:511:21: error: 'SYSCALL_MAX_ARGS' undeclared (first use in this function); did you mean 'SYSCALL_ALIAS'?
unsigned long arg[SYSCALL_MAX_ARGS];
^~~~~~~~~~~~~~~~
SYSCALL_ALIAS
kernel/isolation.c:511:21: note: each undeclared identifier is reported only once for each function it appears in
kernel/isolation.c:511:17: warning: unused variable 'arg' [-Wunused-variable]
unsigned long arg[SYSCALL_MAX_ARGS];
^~~
make[1]: *** [scripts/Makefile.build:267: kernel/isolation.o] Error 1
make[1]: *** Waiting for unfinished jobs....
make: *** [Makefile:1683: kernel] Error 2
quick search:
grep -IHrn SYSCALL_MAX_ARGS
arch/arm/include/asm/syscall.h:54:#define SYSCALL_MAX_ARGS 7
arch/arm64/include/asm/syscall.h:53:#define SYSCALL_MAX_ARGS 6
arch/xtensa/include/asm/syscall.h:57:#define SYSCALL_MAX_ARGS 6
arch/x86/include/asm/syscall.h:91:#define SYSCALL_MAX_ARGS 6
arch/nds32/include/asm/syscall.h:125:#define SYSCALL_MAX_ARGS 6
kernel/isolation.c:511: unsigned long arg[SYSCALL_MAX_ARGS];
my fix:
From: Alex Belits <hidden> Date: 2020-03-08 03:48:45
From: Chris Metcalf <redacted>
This commit adds task isolation hooks as follows:
- __handle_domain_irq() generates an isolation warning for the
local task
- irq_work_queue_on() generates an isolation warning for the remote
task being interrupted for irq_work
- generic_exec_single() generates a remote isolation warning for
the remote cpu being IPI'd
- smp_call_function_many() generates a remote isolation warning for
the set of remote cpus being IPI'd
Calls to task_isolation_remote() or task_isolation_interrupt() can
be placed in the platform-independent code like this when doing so
results in fewer lines of code changes, as for example is true of
the users of the arch_send_call_function_*() APIs. Or, they can be
placed in the per-architecture code when there are many callers,
as for example is true of the smp_send_reschedule() call.
A further cleanup might be to create an intermediate layer, so that
for example smp_send_reschedule() is a single generic function that
just calls arch_smp_send_reschedule(), allowing generic code to be
called every time smp_send_reschedule() is invoked. But for now, we
just update either callers or callees as makes most sense.
Signed-off-by: Chris Metcalf <redacted>
[abelits@marvell.com: adapted for kernel 5.6]
Signed-off-by: Alex Belits <redacted>
---
kernel/irq/irqdesc.c | 9 +++++++++
kernel/irq_work.c | 5 ++++-
kernel/smp.c | 6 +++++-
3 files changed, 18 insertions(+), 2 deletions(-)
@@ -466,6 +469,7 @@ static void smp_call_function_many_cond(const struct cpumask *mask,}/* Send a message to all CPUs in the map */+task_isolation_remote_cpumask(cfd->cpumask_ipi,"IPI function");arch_send_call_function_ipi_mask(cfd->cpumask_ipi);if(wait){
From: Alex Belits <hidden> Date: 2020-03-08 03:50:17
From: Chris Metcalf <redacted>
In prepare_exit_to_usermode(), run cleanup for tasks exited from
isolation and call task_isolation_start() for tasks with
TIF_TASK_ISOLATION.
In syscall_trace_enter_phase1(), add the necessary support for
reporting syscalls for task-isolation processes.
Add task_isolation_remote() calls for the kernel exception types
that do not result in signals, namely non-signalling page faults.
Signed-off-by: Chris Metcalf <redacted>
[abelits@marvell.com: adapted for kernel 5.6]
Signed-off-by: Alex Belits <redacted>
---
arch/x86/Kconfig | 1 +
arch/x86/entry/common.c | 15 +++++++++++++++
arch/x86/include/asm/apic.h | 3 +++
arch/x86/include/asm/thread_info.h | 4 +++-
arch/x86/kernel/apic/ipi.c | 2 ++
arch/x86/mm/fault.c | 4 ++++
6 files changed, 28 insertions(+), 1 deletion(-)
@@ -92,6 +92,7 @@ struct thread_info {#define TIF_NOCPUID 15 /* CPUID is not accessible in userland */#define TIF_NOTSC 16 /* TSC is not accessible in userland */#define TIF_IA32 17 /* IA32 compatibility process */+#define TIF_TASK_ISOLATION 18 /* task isolation enabled for task */#define TIF_NOHZ 19 /* in adaptive nohz mode */#define TIF_MEMDIE 20 /* is terminating due to OOM killer */#define TIF_POLLING_NRFLAG 21 /* idle is polling for TIF_NEED_RESCHED */
@@ -1483,6 +1484,9 @@ void do_user_addr_fault(struct pt_regs *regs,perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MIN,1,regs,address);}+/* No signal was generated, but notify task-isolation tasks. */+task_isolation_interrupt("page fault at %#lx",address);+check_v8086_mode(regs,address,tsk);}NOKPROBE_SYMBOL(do_user_addr_fault);
From: Alex Belits <hidden> Date: 2020-03-08 03:51:30
From: Chris Metcalf <redacted>
In do_notify_resume(), call task_isolation_start() for
TIF_TASK_ISOLATION tasks. Add _TIF_TASK_ISOLATION to _TIF_WORK_MASK,
and define a local NOTIFY_RESUME_LOOP_FLAGS to check in the loop,
since we don't clear _TIF_TASK_ISOLATION in the loop.
We instrument the smp_send_reschedule() routine so that it checks for
isolated tasks and generates a suitable warning if needed.
Finally, report on page faults in task-isolation processes in
do_page_faults().
Signed-off-by: Chris Metcalf <redacted>
[abelits@marvell.com: simplified to match kernel 5.6]
Signed-off-by: Alex Belits <redacted>
---
arch/arm64/Kconfig | 1 +
arch/arm64/include/asm/thread_info.h | 5 ++++-
arch/arm64/kernel/ptrace.c | 10 ++++++++++
arch/arm64/kernel/signal.c | 13 ++++++++++++-
arch/arm64/kernel/smp.c | 7 +++++++
arch/arm64/mm/fault.c | 5 +++++
6 files changed, 39 insertions(+), 2 deletions(-)
@@ -1836,6 +1837,15 @@ int syscall_trace_enter(struct pt_regs *regs)return-1;}+/*+*Intaskisolationmode,wemaypreventthesyscallfrom+*running,andifsowealsodeliverasignaltotheprocess.+*/+if(test_thread_flag(TIF_TASK_ISOLATION)){+if(task_isolation_syscall(regs->syscallno)==-1)+return-1;+}+/* Do the secure computing after ptrace; failures should be fast. */if(secure_computing()==-1)return-1;
@@ -543,6 +544,10 @@ static int __kprobes do_page_fault(unsigned long addr, unsigned int esr,*/if(likely(!(fault&(VM_FAULT_ERROR|VM_FAULT_BADMAP|VM_FAULT_BADACCESS)))){+/* No signal was generated, but notify task-isolation tasks. */+if(user_mode(regs))+task_isolation_interrupt("page fault at %#lx",addr);+/**Major/minorpagefaultaccountingisonlydone*once.Ifwegothrougharetry,itisextremely
From: Mark Rutland <mark.rutland@arm.com> Date: 2020-03-09 16:59:53
On Sun, Mar 08, 2020 at 03:50:58AM +0000, Alex Belits wrote:
quoted hunk
From: Chris Metcalf <redacted>
In do_notify_resume(), call task_isolation_start() for
TIF_TASK_ISOLATION tasks. Add _TIF_TASK_ISOLATION to _TIF_WORK_MASK,
and define a local NOTIFY_RESUME_LOOP_FLAGS to check in the loop,
since we don't clear _TIF_TASK_ISOLATION in the loop.
We instrument the smp_send_reschedule() routine so that it checks for
isolated tasks and generates a suitable warning if needed.
Finally, report on page faults in task-isolation processes in
do_page_faults().
Signed-off-by: Chris Metcalf <redacted>
[abelits@marvell.com: simplified to match kernel 5.6]
Signed-off-by: Alex Belits <redacted>
---
arch/arm64/Kconfig | 1 +
arch/arm64/include/asm/thread_info.h | 5 ++++-
arch/arm64/kernel/ptrace.c | 10 ++++++++++
arch/arm64/kernel/signal.c | 13 ++++++++++++-
arch/arm64/kernel/smp.c | 7 +++++++
arch/arm64/mm/fault.c | 5 +++++
6 files changed, 39 insertions(+), 2 deletions(-)
When I previously asked about tracing, I was asking about the format
strings, since we don't bother with that kind of thing elsewhere.
What exactly are these hooks used for? I assume the strings are only
there as a debugging aid?
What about other IRQs? Does we need something in the irqchip driver?
If we need to track that /any/ interrupt was received, I think that
would be better to put in the top-level interrupt exception handler than
to sprinkle hooks into every potential handler.
quoted hunk
+
switch (ipinr) {
case IPI_RESCHEDULE:
scheduler_ipi();
@@ -543,6 +544,10 @@ static int __kprobes do_page_fault(unsigned long addr, unsigned int esr,*/if(likely(!(fault&(VM_FAULT_ERROR|VM_FAULT_BADMAP|VM_FAULT_BADACCESS)))){+/* No signal was generated, but notify task-isolation tasks. */+if(user_mode(regs))+task_isolation_interrupt("page fault at %#lx",addr);
This isn't an interrupt. Why do we need to hook this?
What about /other/ exceptions caused by userspace?
If we need to notify userspace, it would be much more reliable to do so
in the return path.
Thanks,
Mark.
+
/*
* Major/minor page fault accounting is only done
* once. If we go through a retry, it is extremely
--
2.20.1
From: Alex Belits <hidden> Date: 2020-03-08 03:52:53
From: Francis Giraldeau <redacted>
This patch is a port of the task isolation functionality to the arm 32-bit
architecture. The task isolation needs an additional thread flag that
requires to change the entry assembly code to accept a bitfield larger than
one byte. The constants _TIF_SYSCALL_WORK and _TIF_WORK_MASK are now
defined in the literal pool. The rest of the patch is straightforward and
reflects what is done on other architectures.
To avoid problems with the tst instruction in the v7m build, we renumber
TIF_SECCOMP to bit 8 and let TIF_TASK_ISOLATION use bit 7.
Signed-off-by: Francis Giraldeau <redacted>
Signed-off-by: Chris Metcalf <redacted> [with modifications]
[abelits@marvell.com: modified for kernel 5.6, added isolation cleanup]
Signed-off-by: Alex Belits <redacted>
---
arch/arm/Kconfig | 1 +
arch/arm/include/asm/thread_info.h | 10 +++++++---
arch/arm/kernel/entry-common.S | 15 ++++++++++-----
arch/arm/kernel/ptrace.c | 10 ++++++++++
arch/arm/kernel/signal.c | 13 ++++++++++++-
arch/arm/kernel/smp.c | 4 ++++
arch/arm/mm/fault.c | 8 +++++++-
7 files changed, 51 insertions(+), 10 deletions(-)
@@ -251,7 +255,8 @@ local_restart:ldrr10,[tsk,#TI_FLAGS] @ check for syscall tracingstmdbsp!,{r4,r5}@pushfifthandsixthargs-tstr10,#_TIF_SYSCALL_WORK @ are we tracing syscalls?+ldrr11,=_TIF_SYSCALL_WORK@arewetracingsyscalls?+tstr10,r11bne__sys_traceinvoke_syscalltbl,scno,r10,__ret_fast_syscall
@@ -921,6 +922,15 @@ asmlinkage int syscall_trace_enter(struct pt_regs *regs, int scno)if(test_thread_flag(TIF_SYSCALL_TRACE))tracehook_report_syscall(regs,PTRACE_SYSCALL_ENTER);+/*+*Intaskisolationmode,wemaypreventthesyscallfrom+*running,andifsowealsodeliverasignaltotheprocess.+*/+if(test_thread_flag(TIF_TASK_ISOLATION)){+if(task_isolation_syscall(scno)==-1)+return-1;+}+/* Do seccomp after ptrace; syscall may have changed. */#ifdef CONFIG_HAVE_ARCH_SECCOMP_FILTERif(secure_computing()==-1)
@@ -332,8 +333,13 @@ do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)/**Handlethe"normal"casefirst-VM_FAULT_MAJOR*/-if(likely(!(fault&(VM_FAULT_ERROR|VM_FAULT_BADMAP|VM_FAULT_BADACCESS))))+if(likely(!(fault&(VM_FAULT_ERROR|VM_FAULT_BADMAP|+VM_FAULT_BADACCESS)))){+/* No signal was generated, but notify task-isolation tasks. */+if(user_mode(regs))+task_isolation_interrupt("page fault at %#lx",addr);return0;+}/**Ifweareinkernelmodeatthispoint,we
From: Alex Belits <hidden> Date: 2020-03-08 03:54:07
From: Yuri Norov <redacted>
For nohz_full CPUs the desirable behavior is to receive interrupts
generated by tick_nohz_full_kick_cpu(). But for hard isolation it's
obviously not desirable because it breaks isolation.
This patch adds check for it.
Signed-off-by: Yuri Norov <redacted>
[abelits@marvell.com: updated, only exclude CPUs running isolated tasks]
Signed-off-by: Alex Belits <redacted>
---
kernel/time/tick-sched.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
From: Alex Belits <hidden> Date: 2020-03-08 03:55:04
From: Yuri Norov <redacted>
If CPU runs isolated task, there's no any backlog on it, and
so we don't need to flush it. Currently flush_all_backlogs()
enqueues corresponding work on all CPUs including ones that run
isolated tasks. It leads to breaking task isolation for nothing.
In this patch, backlog flushing is enqueued only on non-isolated CPUs.
Signed-off-by: Yuri Norov <redacted>
[abelits@marvell.com: use safe task_isolation_on_cpu() implementation]
Signed-off-by: Alex Belits <redacted>
---
net/core/dev.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
From: Alex Belits <hidden> Date: 2020-03-08 03:56:00
From: Yuri Norov <redacted>
CPUs running isolated tasks are in userspace, so they don't have to
perform ring buffer updates immediately. If ring_buffer_resize()
schedules the update on those CPUs, isolation is broken. To prevent
that, updates for CPUs running isolated tasks are performed locally,
like for offline CPUs.
A race condition between this update and isolation breaking is avoided
at the cost of disabling per_cpu buffer writing for the time of update
when it coincides with isolation breaking.
Signed-off-by: Yuri Norov <redacted>
[abelits@marvell.com: updated to prevent race with isolation breaking]
Signed-off-by: Alex Belits <redacted>
---
kernel/trace/ring_buffer.c | 62 ++++++++++++++++++++++++++++++++++----
1 file changed, 56 insertions(+), 6 deletions(-)
@@ -1701,6 +1702,37 @@ static void update_pages_handler(struct work_struct *work)complete(&cpu_buffer->update_done);}+staticboolupdate_if_isolated(structring_buffer_per_cpu*cpu_buffer,+intcpu)+{+boolrv=false;++if(task_isolation_on_cpu(cpu)){+/*+*CPUisrunningisolatedtask.Sinceitmaylose+*isolationandre-enterkernelsimultaneouslywith+*thisupdate,disablerecordinguntilit'sdone.+*/+atomic_inc(&cpu_buffer->record_disabled);+/* Make sure, update is done, and isolation state is current */+smp_mb();+if(task_isolation_on_cpu(cpu)){+/*+*IfCPUisstillrunningisolatedtask,we+*canbesurethatbreakingisolationwill+*happenwhilerecordingisdisabled,andCPU+*willnottouchthisbufferuntiltheupdate+*isdone.+*/+rb_update_pages(cpu_buffer);+cpu_buffer->nr_pages_to_update=0;+rv=true;+}+atomic_dec(&cpu_buffer->record_disabled);+}+returnrv;+}+/***ring_buffer_resize-resizetheringbuffer*@buffer:thebuffertoresize.
@@ -1784,13 +1816,22 @@ int ring_buffer_resize(struct trace_buffer *buffer, unsigned long size,if(!cpu_buffer->nr_pages_to_update)continue;-/* Can't run something on an offline CPU. */+/*+*Can'trunsomethingonanofflineCPU.+*+*CPUsrunningisolatedtasksdon'thaveto+*updateringbuffersuntiltheyexit+*isolationbecausetheyarein+*userspace.Usetheprocedurethatprevents+*raceconditionwithisolationbreaking.+*/if(!cpu_online(cpu)){rb_update_pages(cpu_buffer);cpu_buffer->nr_pages_to_update=0;}else{-schedule_work_on(cpu,-&cpu_buffer->update_pages_work);+if(!update_if_isolated(cpu_buffer,cpu))+schedule_work_on(cpu,+&cpu_buffer->update_pages_work);}}
@@ -1829,13 +1870,22 @@ int ring_buffer_resize(struct trace_buffer *buffer, unsigned long size,get_online_cpus();-/* Can't run something on an offline CPU. */+/*+*Can'trunsomethingonanofflineCPU.+*+*CPUsrunningisolatedtasksdon'thavetoupdate+*ringbuffersuntiltheyexitisolationbecausethey+*areinuserspace.Usetheprocedurethatprevents+*raceconditionwithisolationbreaking.+*/if(!cpu_online(cpu_id))rb_update_pages(cpu_buffer);else{-schedule_work_on(cpu_id,+if(!update_if_isolated(cpu_buffer,cpu_id))+schedule_work_on(cpu_id,&cpu_buffer->update_pages_work);-wait_for_completion(&cpu_buffer->update_done);+wait_for_completion(&cpu_buffer->update_done);+}}cpu_buffer->nr_pages_to_update=0;
On Sun, Mar 08, 2020 at 03:55:24AM +0000, Alex Belits wrote:
quoted hunk
From: Yuri Norov <redacted>
CPUs running isolated tasks are in userspace, so they don't have to
perform ring buffer updates immediately. If ring_buffer_resize()
schedules the update on those CPUs, isolation is broken. To prevent
that, updates for CPUs running isolated tasks are performed locally,
like for offline CPUs.
A race condition between this update and isolation breaking is avoided
at the cost of disabling per_cpu buffer writing for the time of update
when it coincides with isolation breaking.
Signed-off-by: Yuri Norov <redacted>
[abelits@marvell.com: updated to prevent race with isolation breaking]
Signed-off-by: Alex Belits <redacted>
---
kernel/trace/ring_buffer.c | 62 ++++++++++++++++++++++++++++++++++----
1 file changed, 56 insertions(+), 6 deletions(-)
@@ -1701,6 +1702,37 @@ static void update_pages_handler(struct work_struct *work)complete(&cpu_buffer->update_done);}+staticboolupdate_if_isolated(structring_buffer_per_cpu*cpu_buffer,+intcpu)+{+boolrv=false;++if(task_isolation_on_cpu(cpu)){+/*+*CPUisrunningisolatedtask.Sinceitmaylose+*isolationandre-enterkernelsimultaneouslywith+*thisupdate,disablerecordinguntilit'sdone.+*/+atomic_inc(&cpu_buffer->record_disabled);+/* Make sure, update is done, and isolation state is current */+smp_mb();+if(task_isolation_on_cpu(cpu)){+/*+*IfCPUisstillrunningisolatedtask,we+*canbesurethatbreakingisolationwill+*happenwhilerecordingisdisabled,andCPU+*willnottouchthisbufferuntiltheupdate+*isdone.+*/+rb_update_pages(cpu_buffer);+cpu_buffer->nr_pages_to_update=0;+rv=true;+}+atomic_dec(&cpu_buffer->record_disabled);+}+returnrv;+}+/***ring_buffer_resize-resizetheringbuffer*@buffer:thebuffertoresize.
@@ -1784,13 +1816,22 @@ int ring_buffer_resize(struct trace_buffer *buffer, unsigned long size,if(!cpu_buffer->nr_pages_to_update)continue;-/* Can't run something on an offline CPU. */+/*+*Can'trunsomethingonanofflineCPU.+*+*CPUsrunningisolatedtasksdon'thaveto+*updateringbuffersuntiltheyexit+*isolationbecausetheyarein+*userspace.Usetheprocedurethatprevents+*raceconditionwithisolationbreaking.+*/if(!cpu_online(cpu)){rb_update_pages(cpu_buffer);cpu_buffer->nr_pages_to_update=0;}else{-schedule_work_on(cpu,-&cpu_buffer->update_pages_work);+if(!update_if_isolated(cpu_buffer,cpu))+schedule_work_on(cpu,+&cpu_buffer->update_pages_work);}}
@@ -1829,13 +1870,22 @@ int ring_buffer_resize(struct trace_buffer *buffer, unsigned long size,get_online_cpus();-/* Can't run something on an offline CPU. */+/*+*Can'trunsomethingonanofflineCPU.+*+*CPUsrunningisolatedtasksdon'thavetoupdate+*ringbuffersuntiltheyexitisolationbecausethey+*areinuserspace.Usetheprocedurethatprevents+*raceconditionwithisolationbreaking.+*/if(!cpu_online(cpu_id))rb_update_pages(cpu_buffer);else{-schedule_work_on(cpu_id,+if(!update_if_isolated(cpu_buffer,cpu_id))+schedule_work_on(cpu_id,&cpu_buffer->update_pages_work);-wait_for_completion(&cpu_buffer->update_done);+wait_for_completion(&cpu_buffer->update_done);+}}cpu_buffer->nr_pages_to_update=0;
gcc output:
kernel/trace/ring_buffer.c: In function 'ring_buffer_resize':
kernel/trace/ring_buffer.c:1884:4: warning: this 'if' clause does not guard... [-Wmisleading-indentation]
if (!update_if_isolated(cpu_buffer, cpu_id))
^~
kernel/trace/ring_buffer.c:1887:5: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the 'if'
wait_for_completion(&cpu_buffer->update_done);
^~~~~~~~~~~~~~~~~~~
kernel/trace/ring_buffer.c:1858:4: error: label 'out' used but not defined
goto out;
^~~~
kernel/trace/ring_buffer.c:1868:4: error: label 'out_err' used but not defined
goto out_err;
^~~~
My fix:
@@ -731,9 +731,21 @@ static void do_nothing(void *unused)*/voidkick_all_cpus_sync(void){+structcpumaskmask;+/* Make sure the change is visible before we kick the cpus */smp_mb();-smp_call_function(do_nothing,NULL,1);++preempt_disable();+#ifdef CONFIG_TASK_ISOLATION+cpumask_clear(&mask);+task_isolation_cpumask(&mask);+cpumask_complement(&mask,&mask);+#else+cpumask_setall(&mask);+#endif+smp_call_function_many(&mask,do_nothing,NULL,1);+preempt_enable();}EXPORT_SYMBOL_GPL(kick_all_cpus_sync);
From: Alex Belits <hidden> Date: 2020-03-08 03:57:48
There are various mechanisms that select CPUs for jobs other than
regular workqueue selection. CPU isolation normally does not
prevent those jobs from running on isolated CPUs. When task
isolation is enabled those jobs should be limited to housekeeping
CPUs.
Signed-off-by: Alex Belits <redacted>
---
drivers/pci/pci-driver.c | 9 +++++++
lib/cpumask.c | 53 +++++++++++++++++++++++++---------------
net/core/net-sysfs.c | 9 +++++++
3 files changed, 51 insertions(+), 20 deletions(-)
From: Alex Belits <hidden> Date: 2020-04-09 15:10:52
On Sat, 2020-03-07 at 19:42 -0800, Alex Belits wrote:
This is the updated version of task isolation patchset.
1. Commit messages updated to match changes.
2. Sign-off lines restored from original patches, changes listed
wherever applicable.
3. arm platform -- added missing calls to syscall check and cleanup
procedure after leaving isolation.
4. x86 platform -- added missing calls to cleanup procedure after
leaving isolation.
Another update, addressing CPU state / race conditions.
I believe, I have some usable solution for the problem of both missing
the events and race conditions on isolation entry and exit.
The idea is to make sure that CPU core remains in userspace and runs
userspace code regardless of what is happening in kernel and userspace
in the rest of the system, however any events that results in running
anything other than userspace code will result in CPU core
re-synchronizing with the rest of the system. Then any kernel code,
with the exception of small and clearly defined set of routines that
only perform kernel entry / exit themselves, will run on CPU after all
synchronization is done.
This does require an answer to possible races between isolation entry
/ exit (including isolation breaking on interrupts) and updates that
are normally carried by IPIs. So the solution should involve some
mechanism that limits what runs on CPU in its "stale" state, and
causes inevitable synchronization before the rest of the kernel is
called. This should also include any preemption -- if preemtion
happens in that "stale" state after entering the kernel but before
synchronization is completed, it should still go through
synchronization before running the rest of the kernel.
Then as long as it can be demonstrated that routines running in
"stale" state can safely run in it, and any event that would normally
require IPI, will result in entering the rest of kernel after
synchronization, race would cease to be a problem. Any sequence of
events would result in exactly the same CPU state when hitting the
rest of the kernel, as if CPU processed the update event through IPI.
I was under impression that this is already the case, however after
some closer look it appears that some barriers must be in place to
make sure that the sequence of events is enforced.
There is obviously a question of performance -- we don't want to cause
any additional flushes or add locking in anything
time-critical. Fortunately entering and exiting isolation (as opposed
to events that _potentially_ can call isolation-breaking routines) is
never performance-critical, it's what starts and ends a task that has
no performance-critical communication with kernel. So if a CPU that
has isolated task on it is running kernel code, it means that either
the task is not isolated yet (we are exiting to userspace), or it is
no longer running anything performance-critical (intentionally on exit
from isolation, or unintentionally on isolation breaking event).
Isolation state is read-mostly, and we would prefer RCU for that if we
can guarantee that "stale" state remains safe in all code that runs
until synchronization happen. I am not sure of that, so I tried to
make something more straightforward, however I might be wrong, and
RCU-ifying exit from isolation may be a better way do do it.
For now I want to make sure that there is some clearly defined small
amount of kernel code that runs before the inevitable synchronization,
and that code is unaffected by "stale" state.
I have tried to track down all call paths from kernel entry points
to the call of fast_task_isolation_cpu_cleanup(), and will post those
separately. It's possible that all architecture-specific code already
follows some clearly defined rules about this for other reasons,
however I am not that familiar with all of it, and tried to check if
existing implementation is always safe for running in "stale" state
before everything that makes task isolation call its cleanup. For now,
this is the implementation that assumes that "stale" state is safe for
kernel entry.
From: Alex Belits <hidden> Date: 2020-04-09 15:16:31
In commit f01f17d3705b ("mm, vmstat: make quiet_vmstat lighter")
the quiet_vmstat() function became asynchronous, in the sense that
the vmstat work was still scheduled to run on the core when the
function returned. For task isolation, we need a synchronous
version of the function that guarantees that the vmstat worker
will not run on the core on return from the function. Add a
quiet_vmstat_sync() function with that semantic.
Signed-off-by: Chris Metcalf <redacted>
Signed-off-by: Alex Belits <redacted>
---
include/linux/vmstat.h | 2 ++
mm/vmstat.c | 9 +++++++++
2 files changed, 11 insertions(+)
From: Alex Belits <hidden> Date: 2020-04-09 15:17:13
This function checks to see if a vmstat worker is not running,
and the vmstat diffs don't require an update. The function is
called from the task-isolation code to see if we need to
actually do some work to quiet vmstat.
Signed-off-by: Chris Metcalf <redacted>
Signed-off-by: Alex Belits <redacted>
---
include/linux/vmstat.h | 2 ++
mm/vmstat.c | 10 ++++++++++
2 files changed, 12 insertions(+)
From: Alex Belits <hidden> Date: 2020-04-09 15:18:26
Some architectures implement memory synchronization instructions for instruction cache. Make a separate kind of barrier that calls them.
Signed-off-by: Alex Belits <redacted>
---
arch/arm/include/asm/barrier.h | 2 ++
arch/arm64/include/asm/barrier.h | 2 ++
include/asm-generic/barrier.h | 4 ++++
3 files changed, 8 insertions(+)