CFS bandwidth control

https://lwn.net/Articles/428230/

Jonathan Corbet

February 16, 2011


CFS 스케쥴러는 CPU를 두고 경쟁하는 프로세스들에게 프로세스들의 CPU utilization은 거의 같게 유지하며 CPU 시간을 나눠주기 위해 최선을 다한다. 하지만 시스템에 남은 CPU 시간이 있을때도 스케쥴러가 프로세스의 CPU utilization을  동일하게 유지하면서 CPU 시간을 나눠줄 수 있다고는 할 수 없다. 스케쥴러는 CPU를 idle 상태로 두는 대신, 남은 CPU 시간을 프로세스들에게 준다. 이런 접근법은 합당하다. 다만 더이상 CPU를 원하는 프로세스가 없을때 runnable 상태의 프로세스를 throttling하는 것에는 이유가 없다. 

The CFS scheduler does its best to divide the available CPU time between contending processes, keeping the CPU utilization of each about the same. The scheduler will not, however, insist on equal utilization when there is free CPU time available; rather than let the CPU go idle, the scheduler will give any left-over time to processes which can make use of it. This approach makes sense; there is little point in throttling runnable processes when nobody else wants the CPU anyway.


때때로 이렇게 동작하는 것을 시스템관리자가 원하는 경우는 제외한다. 이 프로세스들이 한 고객에 속해 있고 고객은 오직 특정 양의 CPU 시간에 대한 비용만 지불했거나 프로세스들간의 엄격한 CPU 자원 사용을 통제할 필요가 있는 상황에 있을 경우가 있다. 이 경우 프로세스 그룹이나 프로세스가 사용할 CPU 시간의 maximum share를 제한하는 것은 바람직할 수 있다. CFS 스케줄러는 이런식으로는 CPU 사용을 제한할 수는 없다. 하지만 Paul Turner에 의해 작성된 CFS bandwidth control 패치를 사용하면 이 상황을 바꿀 수 있다.

Except that, sometimes, that’s exactly what a system administrator may want to do. Limiting the maximum share of CPU time that a process (or group of processes) may consume can be desirable if those processes belong to a customer who has only paid for a certain amount of CPU time or in situations where it is necessary to provide strict resource-use isolation between processes. The CFS scheduler cannot limit CPU use in that manner, but the CFS bandwidth control patches, posted by Paul Turner, may change that situation.


이 패치는 CPU cgroup 메카니즘에 2개의 새로운 file을 추가한다: cpu.cfs_period_us는 태스크 그룹의 CPU 사용량을 통제할 주기(period)를 정의한다. cpu.cfs_quota_us는 태스크 그룹이 주기동안 얼마나 많은 CPU 시간을 사용할 수 있는지를 제어한다. 2개의 새로운 kernel knob을 사용하면 관리자는 쉽게 특정 태스크그룹이 지정된 양의 CPU시간만 쓰도록 제한할 수 있다. 그리고 이 제한이 금지되는 최소한의 CPU시간도 제어할 수 있다.

This patch adds a couple of new control files to the CPU control group mechanism: cpu.cfs_period_us defines the period over which the group’s CPU usage is to be regulated, and cpu.cfs_quota_us controls how much CPU time is available to the group over that period. With these two knobs, the administrator can easily limit a group to a certain amount of CPU time and also control the granularity with which that limit is enforced.


Paul의 패치는 이문제만 해결하는게 목적이 아니다. CFS의 Bharata B Rao가 제출한 hard limits patch set는 비록 구현은 다르지만 거의 동일한 기능을 제공한다.  hard limits 패치는 CPU를 사용하는것을 제한하기 위해 실시간스케쥴러의 bandwidth-limiting code의 일부를 재사용하려고 했다.

Paul’s patch is not the only one aimed at solving this problem; the CFS hard limits patch set from Bharata B Rao provides nearly identical functionality. The implementation is different, though; the hard limits patch tries to reuse some of the bandwidth-limiting code from the realtime scheduler to impose the limits.


Paul은 이 코드를 재사용할 때의 오버헤드와 CPU 시간이 거의 모두 예약된 상황에서 잘 동작할지에 대해서도 우려를 나타냈다. 그리고 이런 우려는 실제로 일어났다. 2010년 초 이후로는 hard limits 패치는 더이상 업데이트되지 않았다. 따라서 CFS bandwidth control patch가 이런 기능을 메인라인 커널에 올리기에는 적합해 보였다.

Paul has expressed concerns about the overhead of using this code and how well it will work in situations where the CPU is almost fully subscribed. These concerns appear to have carried the day – there has not been a hard limits patch posted since early 2010. So the CFS bandwidth control patches look like the form this functionality will take in the mainline.

CFS Bandwidth Control


이 문서는 오직 SCHED_NORMAL의 CPU bandwidth 제어에 대해서만 다룬다. SCHED_RT의 경우는 Documentation/scheduler/sched-rt-group.txt에서 다룬다.

[ This document only discusses CPU bandwidth control for SCHED_NORMAL. The SCHED_RT case is covered in Documentation/scheduler/sched-rt-group.txt ]


CFS bandwidth 제어기능은 CONFIG_FAIR_GROUP_SCHED의 확장된 기능이며, 태스크 그룹이나 계층도가 사용가능한 최대 CPU bandwidth를 지정할 수 있게 해준다.

CFS bandwidth control is a CONFIG_FAIR_GROUP_SCHED extension which allows the specification of the maximum CPU bandwidth available to a group or hierarchy.


태스크그룹의 CPU bandwidth는 quota와 period를 사용해서 지정한다. 태스크 그룹은 주어진 period (ms)동안에  최대 quota ms만큼의 CPU 시간을 사용할 수 있다. 만약 태스크 그룹이 자신의 CPU bandwidth를 초과하면(period동안 사용할 quota를 다 사용했을 경우), 계층도에 속한 태스크들은 CPU 사용이 제한되며(be throttled) 다음 period가 돌아올 때까지는 다시 실행될 수 없다.

The bandwidth allowed for a group is specified using a quota and period. Within each given “period” (microseconds), a group is allowed to consume only up to “quota” microseconds of CPU time. When the CPU bandwidth consumption of a group exceeds this limit (for that period), the tasks belonging to its hierarchy will be throttled and are not allowed to run again until the next period.


태스크 그룹이 사용하지 못하고 남은 runtime은 전역적으로 관리되고 각 period의 끝에서 지정된 quota만큼 갱신된다.  thread들이 사용한 bandwidth는 사용할 때마다 cpu-local “silos”에 전달된다. 매번 전달할 양은 조정가능하고 “slice”로 표현된다

A group’s unused runtime is globally tracked, being refreshed with quota units above at each period boundary. As threads consume this bandwidth it is transferred to cpu-local “silos” on a demand basis. The amount transferred within each of these updates is tunable and described as the “slice”.


Management

Quota와 Period는 cgroupfs의 cpu subsystem에서 관리된다.

Quota and period are managed within the cpu subsystem via cgroupfs.


  • cpu.cfs_quoat_us: period동안 사용할 수 있는 시간(the total available run-time within a period(in microseconds))
  • cpu.cfs_period_us: 실행하는 period(the length of a period (in microseconds))
  • cpu.stat: throttling 통계(exports throttling statistics [explained further below])


기본값은 아래와 같다(The default values are):


cpu.cfs_period_us=100ms

cpu.cfs_quota_us=-1

cpu.cfs_quota_us 값이 -1이면 태스크 그룹에 bandwidth 제한이 없음을 나타내며, bandwidth 제약에서 자유로운 태스크 그룹으로 표현한다. 이것은 일이 없을때만 쉬는(work-conserving) CFS의 전통적인 동작을 나타낸다.

A value of -1 for cpu.cfs_quota_us indicates that the group does not have any bandwidth restriction in place, such a group is described as an unconstrained bandwidth group. This represents the traditional work-conserving behavior for CFS.


파일에 (유효한)양수 값을 쓰면 태스크그룹이 bandwidth 제한을 지정한 것처럼 동작한다. 최소한의 quota, period 값은 1ms이다. period의 최대값은 1초다. bandwidth 제한이 계층구조에 적용되면 추가적인 제한이 발생한다. 자세한 내용은 아래에서 설명하겠다.

Writing any (valid) positive value(s) will enact the specified bandwidth limit. The minimum quota allowed for the quota or period is 1ms. There is also an upper bound on the period length of 1s. Additional restrictions exist when bandwidth limits are used in a hierarchical fashion, these are explained in more detail below.


cpu.cfs_quota_us에 음수 값을 쓰면 bandwidth 제한이 없어지고 태스크 그룹은 다시 bandwidth 제한이 없는 자유로운 상태로 되돌아 간다. 태스크 그룹의 bandwidth 제한이 갱신되면 태스크 그룹에 걸린 제한은 해제된다.

Writing any negative value to cpu.cfs_quota_us will remove the bandwidth limit and return the group to an unconstrained state once more. Any updates to a group’s bandwidth specification will result in it becoming unthrottled if it is in a constrained state.


System wide settings

효율성을 위해 runtime은 global pool과 cpu local “silos” 사이에서 묶어서 보내는 방식으로 전달된다. 이런 방식은 큰 시스템에서의 전역적으로 가해지는 accounting pressure를 많이 줄여줬다.  매번 전달되는 총량은 slice로 나타낸다. 

For efficiency run-time is transferred between the global pool and CPU local “silos” in a batch fashion. This greatly reduces global accounting pressure on large systems. The amount transferred each time such an update is required is described as the “slice”.


아래는 procfs에 있는 tunable이다.

This is tunable via procfs:


/proc/sys/kernel/sched_cfs_bandwidth_slice_us (default=5ms)

작은 값의 slice를 사용하면 더욱 세밀하게 bandwidth 사용을 제어할 수 있다. 반면에 큰 값의 slice는 전송시의 오버헤드를 줄여준다.

Larger slice values will reduce transfer overheads, while smaller values allow for more fine-grained consumption.


통계(Statistics)

태스크 그룹의 bandwidth 통계는 cpu.stat의 필드 3개를 통해 노출된다.

A group’s bandwidth statistics are exported via 3 fields in cpu.stat.


  • cpu.stat:
    • nr_periods: 지나간 주기의 전체 횟수(Number of enforcement intervals that have elapsed.)
    • nr_throttled: 태스크 그룹에 throttling이 걸린 전체 횟수(Number of times the group has been throttled/limited.)
    • throttled_time: 태스크 그룹의 entity들이 throttling된 전체 시간(ns)(The total time duration (in nanoseconds) for which entities of the group have been throttled.)


이 인터페이스는 읽기전용이다.

This interface is read-only.


계층구조에서의 고려사항(Hierarchical considerations)

인터페이스는 각 entity의 bandwidth 제한이 늘 달성되게 한다. 바로 제일 큰 자식의 bandwidth 가 부모의 bandwidth보다 크지 않도록 제한하는 방식을 쓴다. 하지만 전체 합이 부모의 것보다 큰 경우는 명시적으로 자식들의 bandwidth를 다 쓸 수 있는 방식으로 허용된다. 

The interface enforces that an individual entity’s bandwidth is always attainable, that is: max(c_i) <= C. However, over-subscription in the aggregate case is explicitly allowed to enable work-conserving semantics within a hierarchy.


자식들의 bandwidth합이 부모의 것을 넘을 경우가 한 예이다.

e.g. \Sum (c_i) may exceed C

[ Where C is the parent’s bandwidth, and c_i its children ]


태스크 그룹이 throttling되는 경우에는 2가지가 있다.

There are two ways in which a group may become throttled:


  1. 주기동안 자신의 할당량을 전부 사용한 경우(it fully consumes its own quota within a period)
  2. 주기동안 부모가 할당량을 전부 사용한 경우(a parent’s quota is fully consumed within its period)


case b의 경우, 자식의 런타임이 남아있더라도 부모의 런타임이 재충전되기 전까지는 자식은 실행할 수 없다.

In case b) above, even though the child may have runtime remaining it will not

be allowed to until the parent’s runtime is refreshed.


예제(Examples)

1. 태스크 그룹의 runtime을 1개의 cpu에서 제한하는 방법(Limit a group to 1 CPU worth of runtime.)


만약 period가 250ms이고 quota 또한 250ms라면, 태스크 그룹은 매 250ms마다 1개의 CPU 시간을 얻을 것이다.

If period is 250ms and quota is also 250ms, the group will get 1 CPU worth of runtime every 250ms.


# echo 250000 > cpu.cfs_quota_us /* quota = 250ms */

# echo 250000 > cpu.cfs_period_us /* period = 250ms */

2. multi cpu 머신에서 태스크 그룹의 runtime을 2개의 cpu로 제한하는 방법.(Limit a group to 2 CPUs worth of runtime on a multi-CPU machine.)

period가 500ms이고 quota가 1000ms라면, 태스크 그룹은 매 500ms마다 2개의 cpu 시간을 얻을 수 있다. 

With 500ms period and 1000ms quota, the group can get 2 CPUs worth of runtime every 500ms.


# echo 1000000 > cpu.cfs_quota_us /* quota = 1000ms */

# echo 500000 > cpu.cfs_period_us /* period = 500ms */


더 큰 값의 period를 사용하면 증가된 burst capacity를 가능케 한다. 

The larger period here allows for increased burst capacity.


3. CPU 1개의 시간 20%를 사용하도록 태스크 그룹을 제한하는 방법(Limit a group to 20% of 1 CPU.)

period가 50ms일 때, 10ms의 quota는 CPU 1개의 20% 시간과 동일하다.

With 50ms period, 10ms quota will be equivalent to 20% of 1 CPU.


# echo 10000 > cpu.cfs_quota_us /* quota = 10ms */

# echo 50000 > cpu.cfs_period_us /* period = 50ms */


작은 값의 period를 사용하면 burst capacity를 포기하는 대신 일관된 응답시간을 보장할 수 있다.

By using a small period here we are ensuring a consistent latency response at the expense of burst capacity.

페이지 오너: 할당된 페이지의 소유자 추적하기
page owner: Tracking about who allocated each page


 * 도입부(Introduction)

페이지 오너는 각 페이지를 누가 할당했는지 추적하는 기능이다. 이 기능은 메모리 누수를 디버그하거나 메모리 hogger를 찾는 데 사용할 수 있다. 페이지 할당이 발생하면 호출 스택 및 페이지의 order정보와 같은 할당 정보가 각 페이지의 특정 장소에 저장된다. 모든 페이지의 상태를 알아야 할 때 이 정보를 통해 분석 할 수 있다.

page owner is for the tracking about who allocated each page. It can be used to debug memory leak or to find a memory hogger. When allocation happens, information about allocation such as call stack and order of pages is stored into certain storage for each page. When we need to know about status of all pages, we can get and analyze this information.


우리는 이미 페이지 할당/해제를 추적하는 tracepoint를 가지고 있지만 각 페이지를 할당하는 오너를 분석하기 위해 이를 사용하는 것은 다소 복잡하다. 사용자 공간 프로그램이 시작될 때까지 중복을 방지하기 위해 trace buffer를 확대해야 한다. 또한 실행 된 프로그램은 추후 분석을 위해 trace buffer를 계속해서 덤프하므로 메모리를 유지하는 것보다 디버깅에 좋지 않은 가능성으로 시스템 동작을 변경하다.

Although we already have tracepoint for tracing page allocation/free, using it for analyzing who allocate each page is rather complex. We need to enlarge the trace buffer for preventing overlapping until userspace program launched. And, launched program continually dump out the trace buffer for later analysis and it would change system behviour with more possibility rather than just keeping it in memory, so bad for debugging.


페이지 오너는 다양한 용도로 사용될 수 있다. 예를 들어, 각 페이지의 gfp 플래그 정보를 통해 정확한 fragmentation 통계를 얻을 수 있다. 페이지 오너가 활성화 된 경우 이미 구현되고 활성화된다. 다른 용도로 사용하는 것도 환영한다.

page owner can also be used for various purposes. For example, accurate fragmentation statistics can be obtained through gfp flag information of each page. It is already implemented and activated if page owner is enabled. Other usages are more than welcome.


페이지 오너는 기본적으로 사용이 비활성화되어 있다. 따라서 페이지 오너를 사용하고 싶다면 “page_owner=on”을 부팅 파라미터에 추가해야 한다. 커널이 페이지 오너로 빌드되었지만 부트 파라미터로 활성화하지 않아서 페이지 오너가 비활성화되었을 때에는, 런타임 오버 헤드가 거의 없다. 런타임에 사용하지 않도록 설정하면 소유자 정보를 저장하는데 메모리가 필요하지 않으므로 런타임 메모리 오버 헤드가 없다. 

page owner is disabled in default. So, if you’d like to use it, you need to add “page_owner=on” into your boot cmdline. If the kernel is built with page owner and page owner is disabled in runtime due to no enabling boot option, runtime overhead is marginal. If disabled in runtime, it doesn’t require memory to store owner information, so there is no runtime memory overhead.


그리고 페이지 오너는 페이지 할당 자 hotpath에 두 개의 가능성이 적은 브랜치를 삽입하고, 활성화되지 않은 경우 페이지 오너가 없는 커널처럼 할당을 수행한다. static keys jump label patching 기능을 사용할 수있는 경우 특히 이 두 가지가 할당 성능에 영향을 미치지 않는다. 다음은 이 기능으로 인한 커널의 코드 크기 변경을 나타낸다.

And, page owner inserts just two unlikely branches into the page allocator hotpath and if not enabled, then allocation is done like as the kernel without page owner. These two unlikely branches should not affect to allocation performance, especially if the static keys jump label patching functionality is available. Following is the kernel’s code size change due to this facility.


페이지 오너가 없는 경우(Without page owner)

text data bss dec hex filename

40662 1493 644 42799 a72f mm/page_alloc.o


페이지 오너가 있는 경우(With page owner)

text data bss dec hex filename

40892 1493 644 43029 a815 mm/page_alloc.o

1427 24 8 1459 5b3 mm/page_ext.o

2722 50 0 2772 ad4 mm/page_owner.o


대략 4KB 코드가 합계로 추가되지만, page_alloc.o는 230 바이트 씩 증가하며 그 중 절반만이 hotpath에 있다. 페이지 오너를 추가한 커널을 빌드하고 필요하다면 페이지 오너를 활성화하면 커널 메모리 문제를 디버깅하는 데 아주 좋다.

Although, roughly, 4 KB code is added in total, page_alloc.o increase by 230 bytes and only half of it is in hotpath. Building the kernel with page owner and turning it on if needed would be great option to debug kernel memory problem.


세부적인 구현으로 인해 알아야 할 것이 하나 있다. 페이지 오너는 struct page extension로부터 정보를 메모리에 저장한다. 이 메모리는 sparse 메모리 시스템에서 페이지 할당자보다 조금 늦게 초기화된다. 따라서 초기화될 때까지, 많은 페이지가 할당될 수 있으며 오너 정보가 없게 된다. 

There is one notice that is caused by implementation detail. page owner stores information into the memory from struct page extension. This memory is initialized some time later than that page allocator starts in sparse memory system, so, until initialization, many pages can be allocated and they would have no owner information.


이를 해결하기 위해 초기 할당된 페이지를 조사하고 초기화 단계에서 할당 된 것으로 표시하다. 비록 페이지의 오너가 정확한지를 의미하지는 않지만 페이지가 할당되었는지 아닌지는 정확하게 알 수 있다. 2GB 메모리의 x86-64 VM 머신에서 13343개의 초기 할당 페이지는 주로 page extension 구조체에 할당되지만 어쨌든, 모든 페이지가 추적중인 상태가 된다.

To fix it up, these early allocated pages are investigated and marked as allocated in initialization phase. Although it doesn’t mean that they have the right owner information, at least, we can tell whether the page is allocated or not, more accurately. On 2GB memory x86-64 VM box, 13343 early allocated pages are catched and marked, although they are mostly allocated from struct page extension feature. Anyway, after that, no page is left in un-tracking state.


* 사용법(Usage)

1) 유저 헬퍼를 빌드하기(Build user-space helper)

cd tools/vm

make page_owner_sort

2) 커널에서 페이지오너를 활성화하기(Enable page owner)


커널 부트 파라미터로 “page_owner=on”을 추가하자.

Add “page_owner=on” to boot cmdline.


3) 디버깅하고 싶은 시나리오를 실행하기(Do the job what you want to debug)


4) 페이지 오너 정보를 분석하기(Analyze information from page owner)

cat /sys/kernel/debug/page_owner > page_owner_full.txt

grep -v ^PFN page_owner_full.txt > page_owner.txt

./page_owner_sort page_owner.txt sorted_page_owner.txt

sorted_page_owner.txt 에서 각 페이지를 누가 할당했는지에 대한 결과를 보자.

See the result about who allocated each page in the sorted_page_owner.txt.

Atomic context and kernel API design

https://lwn.net/Articles/274695

By Jonathan Corbet

March 25, 2008


API는 지킬 수 없는 약속은 삼가해야 한다. 커널의 in_atomic () 매크로를 포함하는 최근의 일화에서 함수가 보이는 것과 다르게 동작할 경우 어떻게 상황이 잘못되는지를 보여준다. 또한 커널 코드 디자에 대한 문서화되지 않은 (근본적인) 측면을 살펴 보는 것도 good excuse이다.

An API should refrain from making promises that it cannot keep. A recent episode involving the kernel's in_atomic() macro demonstrates how things can go wrong when a function does not really do what it appears to do. It is also a good excuse to look at an under-documented (but fundamental) aspect of kernel code design.


커널 코드는 일반적으로 두 가지 기본 컨텍스트 중 하나에서 실행된다. process context는 커널이 (일반적으로) 유저 프로세스 대신 직접 실행될 때 reign한다. 시스템 호출을 구현하는 코드가 하나의 예이다. process context에서 커널이 실행 중일 때 필요하면 sleep 상태로 전환 할 수 있다. 그러나 커널이 atomic context에서 실행될 때, sleep 같은 동작은 허용되지 않는다. 하드웨어 및 소프트웨어 인터럽트를 처리하는 코드는 atomic context의 명확한 예이다.

Kernel code generally runs in one of two fundamental contexts. Process context reigns when the kernel is running directly on behalf of a (usually) user-space process; the code which implements system calls is one example. When the kernel is running in process context, it is allowed to go to sleep if necessary. But when the kernel is running in atomic context, things like sleeping are not allowed. Code which handles hardware and software interrupts is one obvious example of atomic context.


그러나 그것보다 더 많은 것이 있다: 모든 커널 함수는 spinlock을 획득하는 순간 atomic context로 진입한다. spinlock이 구현되는 방식을 감안할 때, 락을 잡고 있는 상황에서 sleep 상태로 전환하는 것은 fatal error이다. 다른 커널 함수가 동일한 락을 얻으려고하면 시스템은 거의 확실히 교착 상태에 빠지게된다.

There is more to it than that, though: any kernel function moves into atomic context the moment it acquires a spinlock. Given the way spinlocks are implemented, going to sleep while holding one would be a fatal error; if some other kernel function tried to acquire the same lock, the system would almost certainly deadlock forever.


"Deadlocking forever"는 유저가 커널에게 기대하는게 아니므로 커널 개발자는 이러한 상황을 피하기 위해 go out of their way하게 된다. 이를 위해 atomic context에서 실행되는 코드는 (1)사용자 공간에 대한 액세스가 없으며, 결정적으로, (2) sleep 상태로 전환하지 않는 등 여러 가지 규칙을 신>중하게 따라야한다. 특정 커널 함수가 호출 될 수있는 문맥을 알지 못하는 경우 문제가 발생할 수 있다. 예를 들어 kmalloc() API는 명시적 인수(GFP_KERNEL 또는 GFP_ATOMIC)를 사용하여 sleep 가능 여부를 지정한다.

"Deadlocking forever" tends not to appear on users' wishlists for the kernel, so the kernel developers go out of their way to avoid that situation. To that end, code which is running in atomic context carefully follows a number of rules, including (1) no access to user space, and, crucially, (2) no sleeping. Problems can result, though, when a particular kernel function does not know which context it might be invoked in. The classic example is kmalloc() and friends, which take an explicit argument (GFP_KERNEL or GFP_ATOMIC) specifying whether sleeping is possible or not.


두 상황에서 최적으로 작동 할 수있는 코드를 작성하고자하는 것이 일반적이다. 일부 개발자는 그러한 코드를 작성하는 동안 <linux/hardirq.h>에서 다음 정의를 우연히 발견 할 수 있다.

The wish to write code which can work optimally in either context is common, though. Some developers, while trying to write such code, may well stumble across the following definitions from <linux/hardirq.h>:


/*
* Are we doing bottom half or hardware interrupt processing?
* Are we in a softirq context? Interrupt context?
*/
#define in_irq()           (hardirq_count())
#define in_softirq()   (softirq_count())
#define in_interrupt() (irq_count())

#define in_atomic()        ((preempt_count() & ~PREEMPT_ACTIVE) != 0)

in_atomic()은 코드의 특정 비트가 특정 시간에 atomic한  방식으로 작동해야하는지 여부를 결정하려고하는 개발자의 요구에 적합 할 것으로 보인다. 커널 소스를 빠르게 검색(grep)한 결과가 말하기를 사실, in_atomic()이 목적과 달리 상당히 다른 장소에서 사용됨을 보여준다. 단 하나의 문제가 있다. 바로 이러한 용도는 거의 틀림없이 모두 잘못되었다는 것이다.
It would seem that in_atomic() would fit the bill for any developer trying to decide whether a given bit of code needs to act in an atomic manner at any specific time. A quick grep through the kernel sources shows that, in fact, in_atomic() has been used in quite a few different places for just that purpose. There is only one problem: those uses are almost certainly all wrong.

in_atomic() 매크로는 선점이 사용 중지되었는지 여부를 확인하며 올바르게 작동하는 것처럼 보인다. 하드웨어 인터럽트와 같은 이벤트 핸들러는 선점을 비활성화하지만 spinlock을 획득한다. 따라서이 테스트는 sleep하면 안되는 모든 경우를 캐치하는 것처럼 보인다. 확실히 이 매크로를 보아온 많은사람들이 위와 같은 결론에 도달했다.
The in_atomic() macro works by checking whether preemption is disabled, which seems like the right thing to do. Handlers for events like hardware interrupts will disable preemption, but so will the acquisition of a spinlock. So this test appears to catch all of the cases where sleeping would be a bad idea. Certainly a number of people who have looked at this macro have come to that conclusion.

그러나 커널선점(CONFIG_PREEMPT?)이 설정되어 있지 않으면 커널은 spinlock을 획득 할 때 "preemption count"를 높이지 않는다. 따라서 이 상황(일반적으로 많은 배포자가 여전히 커널에서 커널선점을 사용할 수 없음, 이 글이 2008년도에 나왔음을 감안해야 함)에서 in_atomic()은 호출 코드가 spinlock을 보유하고 있는지 여부를 알 수 없다. 따라서 spinlock이 유지되는 경우에도 process context를 나타내는 0을 반환한다. 그리고 실제로 커널 코드가 process context에서 실행 중이고 그에 따라 작동한다고 생각할 수도 있다.
But if preemption has not been configured into the kernel in the first place, the kernel does not raise the "preemption count" when spinlocks are acquired. So, in this situation (which is common - many distributors still do not enable preemption in their kernels), in_atomic() has no way to know if the calling code holds any spinlocks or not. So it will return zero (indicating process context) even when spinlocks are held. And that could lead to kernel code thinking that it is running in process context (and acting accordingly) when, in fact, it is not.

이 문제가 주어지면 기능이 왜 처음에 존재하는지, 왜 사람들이 그것을 사용하는지, 그리고 개발자가 실제로 잠들지 않느냐 여부에 대해 어떻게 처리 할 수 있는지 궁금해 할 것이다. 앤드류 모튼 (Andrew Morton)은 첫 번째 질문에 비교적 비밀스러운 방식으로 대답했다.
Given this problem, one might well wonder why the function exists in the first place, why people are using it, and what developers can really do to get a handle on whether they can sleep or not. Andrew Morton answered the first question in a relatively cryptic way:

in_atomic ()은 커널 코어 코드에서만 사용된다. 특수 상황(예 : kmap_atomic ())에서는 pre-preemptible 커널에서도 inc_preempt_count()를 실행하여 kmap_atomic() 내에서 copy_*_user()에 의해 호출 된 아키텍쳐별로 구현된 fault 핸들러에게 알려주고 실패해야한다.
in_atomic() is for core kernel use only. Because in special circumstances (ie: kmap_atomic()) we run inc_preempt_count() even on non-preemptible kernels to tell the per-arch fault handler that it was invoked by copy_*_user() inside kmap_atomic(), and it must fail.

즉, in_atomic()은 특정 저수준 상황에서 작동하지만 더 넓은 맥락에서 사용되는 것을 의미하지는 않다. 다른 곳에서 사용할 수있는 매크로 옆에 hardirq.h를 배치 한 것은 거의 실수였다. Alan Stern이 지적했듯이, Linux Device Drivers가 in_atomic()을 사용하도록 권장한다는 사실이 이 상황을 도왔을 것이다. 귀하의 편집인은 그 책의 저자들이 즉시 해고 될 것을 권고한다.
In other words, in_atomic() works in a specific low-level situation, but it was never meant to be used in a wider context. Its placement in hardirq.h next to macros which can be used elsewhere was, thus, almost certainly a mistake. As Alan Stern pointed out, the fact that Linux Device Drivers recommends the use of in_atomic() will not have helped the situation. Your editor recommends that the authors of that book be immediately sacked.

이러한 실수가 해결되면 커널 코드가 atomic context에서 실행되는지 여부를 결정하는 방법에 대한 질문이 여전히 있다. 진정한 대답은 단지 그렇게 할 수 없다는 것이다. Andrew Morton을 다시 인용하면 :
Once these mistakes are cleared up, there is still the question of just how kernel code should decide whether it is running in an atomic context or not. The real answer is that it just can't do that. Quoting Andrew Morton again:

커널에서 사용하는 일관된 패턴은 호출자가 스케줄 가능한 컨텍스트에서 실행 중인지 여부를 추적하고 필요하면 피호출자에게 알려주는 것이다. 피 호출자는 스스로 해결하지 못한다.
The consistent pattern we use in the kernel is that callers keep track of whether they are running in a schedulable context and, if necessary, they will inform callees about that. Callees don't work it out for themselves.

이 패턴은 커널을 살펴보면 일관성있게 유지되고 있다. 다시 한번 GFP_ 플래그 예제가 이 점에서 두드러집니다. 그러나 커널 개발자들이 이런 방식으로 해야 한다는 것을 이해할 수 있도록 이 관행이 문서화되어 있지 않다는 것도 분명한다. 대부분의 사람들보다 이 이슈를 더 잘 이해하고있는 러스티 러셀 (Rusty Russell)의 최근 포스팅을 참고하자:
This pattern is consistent through the kernel - once again, the GFP_ flags example stands out in this regard. But it's also clear that this practice has not been documented to the point that kernel developers understand that things should be done this way. Consider this recent posting from Rusty Russell, who understands these issues better than most:

이 플래그는 메모리가 즉시 사용 가능하지 않을 때 할당자가 수행해야하는 작업을 나타낸다. 메모리가 해제되거나 스왑 아웃되는 동안 기다리거나(GFP_KERNEL) 즉시 NULL을 반환해야하는지(GFP_ATOMIC) 여부를 나타낸다. 그리고 이 플래그는 완전히 중복되어 있다 : kmalloc()는 스스로 sleep 가능 여부를 알아 낼 수 있다.
This flag indicates what the allocator should do when no memory is immediately available: should it wait (sleep) while memory is freed or swapped out (GFP_KERNEL), or should it return NULL immediately (GFP_ATOMIC). And this flag is entirely redundant: kmalloc() itself can figure out whether it is able to sleep or not.

사실 kmalloc()은 sleep 가능 여부를 스스로 알 수 없다. 호출자가 이를 알려줘야 한다. 이 규칙은 변경될 가능성이 없기 때문에 2.6.26부터 시작하는 일련의 in_atomic() 제거 패치를 기대하자. 이 작업이 완료되면 in_atomic () 매크로를 더 혼란스럽지 않게 안전한 장소로 이동할 수 있다.
In fact, kmalloc() cannot figure out on its own whether sleeping is allowable or not. It has to be told by the caller. This rule is unlikely to change, so expect a series of in_atomic() removal patches starting with 2.6.26. Once that work is done, the in_atomic() macro can be moved to a safer place where it will not create further confusion.


'커널 번역(기사,문서)' 카테고리의 다른 글

[번역] scheduler/sched-bwc.txt  (0) 2018.09.06
[번역] vm/page_owner.txt  (0) 2018.09.05
[번역] vm/slub.txt  (0) 2018.08.30
[번역] scheduler/sched-nice-design.txt  (0) 2018.08.03
[번역] scheduler/sched-stats.txt  (0) 2018.08.03

SLUB에 대한 간단한 유저가이드(Short users guide for SLUB)

SLUB의 기본 철학은 SLAB과 매우 다르다. SLAB는 모든 슬랩 캐시에 대한 디버그 옵션을 활성화하기 위해 커널을 다시 빌드해야 한다. SLUB은 항상 전체 디버깅을 포함하지만 기본적으로 해제되어 있다. SLUB은 전체 시스템 성능에 대한 영향을 피하기 위해 선택한 슬랩에 대해서만 디버깅을 활성화 할 수 있으므로 버그를 찾기가 더 어려워 질 수 있다.

The basic philosophy of SLUB is very different from SLAB. SLAB requires rebuilding the kernel to activate debug options for all slab caches. SLUB always includes full debugging but it is off by default. SLUB can enable debugging only for selected slabs in order to avoid an impact on overall system performance which may make a bug more difficult to find.


디버깅을 전환하려면 “slub_debug”옵션을 커널 명령 행에 추가하면 된다. 그러면 모든 슬랩에 대해 전체 디버깅이 가능하다.

In order to switch debugging on one can add an option “slub_debug” to the kernel command line. That will enable full debugging for all slabs.


슬랩에 대한 통계 데이터를 얻고 슬랩에 작업을 수행하기 위해 보통 “slabinfo”를 사용한다. 기본적으로 slabinfo는 데이터가 들어있는 슬랩만 나열한다. 명령 실행시 추가 옵션은 “slabinfo -h”를 참조하자. slabinfo를 컴파일하려면 아래와 같이 하면된다.

Typically one would then use the “slabinfo” command to get statistical data and perform operation on the slabs. By default slabinfo only lists slabs that have data in them. See “slabinfo -h” for more options when running the command. slabinfo can be compiled with


gcc -o slabinfo tools/vm/slabinfo.c


slabinfo의 일부 동작 모드는 커맨드라인(커널 부트파라미터)에 slub debugging을 활성화하길 요구한다. F.e. slub debugging이 활성화되지 않고서는 추적 정보를 사용할 수 없으며 디버깅이 켜지지 않은 경우에는 부분적인 유효성 검사만 수행 될 수 있다.

Some of the modes of operation of slabinfo require that slub debugging be enabled on the command line. F.e. no tracking information will be available without debugging on and validation can only partially be performed if debugging was not switched on.


좀 더 정교한 slub_debug 사용법(Some more sophisticated uses of slub_debug)

slub_debug에 파라미터를 사용할 수 있다. 파라미터를 지정하지 않으면 full debugging이 활성화된다. 파라미터의 형식은 아래와 같다.

Parameters may be given to slub_debug. If none is specified then full debugging is enabled. Format:


slub_debug=<Debug-Options>

모든 슬랩에 적용되는 옵션을 활성화함(Enable options for all slabs)

slub_debug=<Debug-Options>,<slab name>

특정 슬랩에 적용되는 옵션을 활성화함(Enable options only for select slabs)


사용가능한 디버깅 옵션은 아래와 같다.(Possible debug options are)


  • ‘F’ – sanity check 활성화(Sanity checks on)
    • SLAB_DEBUG_CONSISTENCY_CHECKS를 활성화한다(enables SLAB_DEBUG_CONSISTENCY_CHECKS Sorry SLAB legacy issues)
  • ‘Z’ – 레드존 기능을 활성화(Red zoning)
  • ‘P’ – 포이즈닝 기능을 활성화(Poisoning (object and padding))
  • ‘U’ – 슬랩 할당/해제를 추적하는 기능을 활성화(User tracking (free and alloc))
  • ‘T’ – 슬랩을 추적하는 기능을 활성화(Trace (please only use on single slabs))
  • ‘A’ – ??(Toggle failslab filter mark for the cache)
  • ‘O’ – 최소 slab order를 증가시킬 우려가 있는 슬랩캐시에 대한 디버깅을 비활성화함(Switch debugging off for caches that would have caused higher minimum slab orders)
  • ‘-‘ – 모든 디버깅 기능 비활성화(Switch all debugging off)
    • 커널이 사용하는 CONFIG_SLUB_DEBUG_ON을 비활성화할 때 유용함 (useful if the kernel is configured with CONFIG_SLUB_DEBUG_ON))

예를 들어 sanity 체크와 red zone 만 활성화해서 부팅하기 위해서는 아래와 같이 설정한다.

F.e. in order to boot just with sanity checks and red zoning one would specify:


slub_debug=FZ


dentry 캐시의 문제를 찾기 위해서는 아래와 같이 설정한다.

Trying to find an issue in the dentry cache? Try


slub_debug=,dentry


위와 같이 설정하면 dentry 캐시에 대해서만 디버깅할 수 있다.

to only enable debugging on the dentry cache.


red zone을 활성화하고 추적하는 것은 slab을 재정렬?할 수 있다. 아래와 같이 설정하면 dentry 캐시에만 sanity 체크를 활성화할 수 있다.

Red zoning and tracking may realign the slab. We can just apply sanity checks to the dentry cache with


slub_debug=F,dentry


디버깅 옵션은 메타 데이터(예를 들어, caches with PAGE_SIZE objects sizes) 를 저장하기 위해 결과적으로 slab order를 증가시키는 걸 필요로 할수도 있다. 이런 상황은 메모리가 부족한 상황이나 메모리 단편화가 심한 상황에서 슬랩 할당에러가 발생할 확률을 높힌다. 이런 캐시에 대한 디버깅을 비활성화하려면 아래와 같이 설정한다.

Debugging options may require the minimum possible slab order to increase as a result of storing the metadata (for example, caches with PAGE_SIZE object sizes). This has a higher liklihood of resulting in slab allocation errors in low memory situations or if there’s high fragmentation of memory. To switch off debugging for such caches by default, use


slub_debug=O


커널 커맨드 라인에서 디버깅을 활성화하는 것을 잊어버린 경우: 커널이 동작중일 때 수동으로 디버깅을 활성화할 수 있다. 아래 경로의 내용을 살펴보자.

In case you forgot to enable debugging on the kernel command line: It is possible to enable debugging manually when the kernel is up. Look at the contents of:


/sys/kernel/slab/<slab name>/


write 가능한 파일을 보자. 1을 쓰면 해당 디버그 옵션이 활성화된다. 모든 옵션은 오브젝트가 포함되지 않은 슬랩에 설정할 수 있다. 슬랩에 이미 오브젝트가 있다면 sanitize 체크와 추적을 사용할 수 있다. 다른 옵션을 사용하면 오브젝트의 realign이 발생할 수 있다.

Look at the writable files. Writing 1 to them will enable the corresponding debug option. All options can be set on a slab that does not contain objects. If the slab already contains objects then sanity checks and tracing may only be enabled. The other options may cause the realignment of objects.


주의깊게 추적해야 한다.  많은 정보가 출력될 수 있으며 잘못된 슬랩에 사용하면 멈추지 않을 수 있다.

Careful with tracing: It may spew out lots of information and never stop if used on the wrong slab.


슬랩 캐시 통합기능(Slab merging)

디버그 옵션을 사용하지 않으면 SLUB은 오버 헤드를 줄이고 오브젝트의 cache hotness를 높이기 위해 유사한 슬랩을 병합 할 수 있다. slabinfo -a는 어떤 슬랩이 함께 병합되었는지 표시한다.

If no debug options are specified then SLUB may merge similar slabs together in order to reduce overhead and increase cache hotness of objects. slabinfo -a displays which slabs were merged together.


슬랩 유효성(Slab validation)

SLUB은 slub_debug로 커널을 부팅 한 경우 모든 오브젝트의 유효성을 검사 할 수 있다. 그렇게 하려면 slabinfo 도구가 있어야한다. 도구를 구하면 아래와 같이 사용할 수 있다.

SLUB can validate all object if the kernel was booted with slub_debug. In order to do so you must have the slabinfo tool. Then you can do


slabinfo -v


위 명령어는 모든 슬랩 오브젝트의 유효성을 체크한다. 체크 결과는 syslog로 출력된다.

which will test all objects. Output will be generated to the syslog.


slab debug 없이 부팅된 경우 이 방법은 더 제한된 방식으로 작동한다. 이 경우 slabinfo -v는 모든 도달 가능한 오브젝트를 테스트한다. 보통 이들은 CPU 슬랩과 partial 슬랩에 있다. SLUB는 디버그가 활성화되지 않은 상황에서는 전체 슬랩을 추적하지 않는다.

This also works in a more limited way if boot was without slab debug. In that case slabinfo -v simply tests all reachable objects. Usually these are in the cpu slabs and the partial slabs. Full slabs are not tracked by SLUB in a non debug situation.


슬랩 성능 높히기(Getting more performance)

슬럽의 성능은 partial 슬랩을 처리하는 동안에 잡아야 하는 list_lock에 의해 약간 제한된다. 그 오버 헤드는 각 슬랩에 대한 할당 순서에 의해 결정된다. 할당은 아래의 커널 파라미터의 영향을 받을 수 있다.

To some degree SLUB’s performance is limited by the need to take the list_lock once in a while to deal with partial slabs. That overhead is governed by the order of the allocation for each slab. The allocations can be influenced by kernel parameters:


slub_min_objects=x (default 4)

slub_min_order=x (default 0)

slub_max_order=x (default 3 (PAGE_ALLOC_COSTLY_ORDER))


slub_min_objects 파라미터는 허용가능한 할당 순서를 위해 얼마나 많은 오브젝트가 최소한 하나의 슬랩에 들어가야 하는지를 지정할 수 있다. 일반적으로 슬럽은 발생할 수 있는 lock contention이 발생할 수 있는 list_lock을 사용하는 중앙 자원을 참조하지 않고 이런 류의 할당을 처리할 수 있다.

slub_min_objects allows to specify how many objects must at least fit into one slab in order for the allocation order to be acceptable. In general slub will be able to perform this number of allocations on a slab without consulting centralized resources (list_lock) where contention may occur.


slub_min_order 파라미터는 슬랩의 최소 order를 지정한다. slub_min_objects와 비슷한 효과를 나타낸다.

slub_min_order specifies a minim order of slabs. A similar effect like slub_min_objects.


slub_max_order 파라미터는 slub_min_objects 파라미터가 더이상 체크되지 않을 order를 나타낸다. 이는 슬럽이 큰 크기를 오브젝트를 가진 슬랩 캐시의 slub_min_objects 파라미터에 맞추기 위해 super large order pages를 생성하는 시도를 피할 수 있어서 유용하다. 커맨드라인 파라미터 debug_guardpage_minorder = N (N> 0)을 설정하면 slub_max_order를 0으로 설정하고 슬랩할당시에 사용한 최소한의 possible order를 결정한다.

slub_max_order specified the order at which slub_min_objects should no longer be checked. This is useful to avoid SLUB trying to generate super large order pages to fit slub_min_objects of a slab cache with large object sizes into one high order page. Setting command line parameter debug_guardpage_minorder=N (N > 0), forces setting slub_max_order to 0, what cause minimum possible order of slabs allocation.


슬럽 디버깅 결과(SLUB Debug output)

아래는 슬럽 디버그 출력 샘플이다.

Here is a sample of slub debug output:


====================================================================

BUG kmalloc-8: Redzone overwritten

--------------------------------------------------------------------


INFO: 0xc90f6d28-0xc90f6d2b. First byte 0x00 instead of 0xcc

INFO: Slab 0xc528c530 flags=0x400000c3 inuse=61 fp=0xc90f6d58

INFO: Object 0xc90f6d20 @offset=3360 fp=0xc90f6d58

INFO: Allocated in get_modalias+0x61/0xf5 age=53 cpu=1 pid=554


Bytes b4 0xc90f6d10: 00 00 00 00 00 00 00 00 5a 5a 5a 5a 5a 5a 5a 5a ........ZZZZZZZZ

 Object 0xc90f6d20: 31 30 31 39 2e 30 30 35 1019.005

 Redzone 0xc90f6d28: 00 cc cc cc .

 Padding 0xc90f6d50: 5a 5a 5a 5a 5a 5a 5a 5a ZZZZZZZZ


[<c010523d>] dump_trace+0x63/0x1eb

 [<c01053df>] show_trace_log_lvl+0x1a/0x2f

 [<c010601d>] show_trace+0x12/0x14

 [<c0106035>] dump_stack+0x16/0x18

 [<c017e0fa>] object_err+0x143/0x14b

 [<c017e2cc>] check_object+0x66/0x234

 [<c017eb43>] __slab_free+0x239/0x384

 [<c017f446>] kfree+0xa6/0xc6

 [<c02e2335>] get_modalias+0xb9/0xf5

 [<c02e23b7>] dmi_dev_uevent+0x27/0x3c

 [<c027866a>] dev_uevent+0x1ad/0x1da

 [<c0205024>] kobject_uevent_env+0x20a/0x45b

 [<c020527f>] kobject_uevent+0xa/0xf

 [<c02779f1>] store_uevent+0x4f/0x58

 [<c027758e>] dev_attr_store+0x29/0x2f

 [<c01bec4f>] sysfs_write_file+0x16e/0x19c

 [<c0183ba7>] vfs_write+0xd1/0x15a

 [<c01841d7>] sys_write+0x3d/0x72

 [<c0104112>] sysenter_past_esp+0x5f/0x99

 [<b7f7b410>] 0xb7f7b410

 =======================

FIX kmalloc-8: Restoring Redzone 0xc90f6d28-0xc90f6d2b=0xcc

SLUB에 오염된 오브젝트가 발생하면(전체 탐지를 위해서는 slub_debug 파라미터를 사용해서 커널을 부팅해야 함) 다음 내용이 syslog에 출력된다.

If SLUB encounters a corrupted object (full detection requires the kernel to be booted with slub_debug) then the following output will be dumped into the syslog:


1.발생한 문제에 대한 설명(Description of the problem encountered) 

아래 메세지는 시스템 로그의 시작부분에 출력된다.

This will be a message in the system log starting with


===============================================

BUG <slab cache affected>: <What went wrong>

-----------------------------------------------


INFO: <corruption start>-<corruption_end> <more info>

INFO: Slab <address> <slab information>

INFO: Object <address> <object information>

INFO: Allocated in <kernel function> age=<jiffies since alloc> cpu=<allocated by

 cpu> pid=<pid of the process>

INFO: Freed in <kernel function> age=<jiffies since free> cpu=<freed by cpu>

 pid=<pid of the process>

(오브젝트 할당/해제 정보는 슬랩에 SLAB_STORE_USER가 설정된 경우에만 사용할 수 있으며 slub_debug 파라미터에서 해당 옵션(U)을 설정할 수 있다.)

(Object allocation / free information is only available if SLAB_STORE_USER is set for the slab. slub_debug sets that option)


2.슬랩 오브젝트의 내용(오브젝트가 관련된 경우) The object contents if an object was involved.

다양한 유형의 행이 BUG SLUB 행 결과가 뒤따라 출력될 수 있다.

Various types of lines can follow the BUG SLUB line:


Bytes b4 <address> : <bytes>

문제가 발견 된 오브젝트 앞 몇 바이트를 출력한다. corruption이 오브젝트의 시작주소 전에 멈추지 않는다면 유용 할 수 있다.

Shows a few bytes before the object where the problem was detected. Can be useful if the corruption does not stop with the start of the object.


Object <address> : <bytes>

오브젝트의 바이트단위 크기를 나타냄. 오브젝트가 비활성 상태이면 바이트는 일반적으로 포이즌 영역의 크기를 포함한다. poison 영역에 쓰이지 않는 값이 슬랩 해제 후에 쓰여진 corruption을 나타낸다.

The bytes of the object. If the object is inactive then the bytes typically contain poison values. Any non-poison value shows a corruption by a write after free.


Redzone <address> : <bytes>

슬랩 오브젝트 뒤에 위치하는 레드존을 나타낸다. 레드존은 오브젝트 영역 다음 영역에 대한 쓰기를 감지하는데 사용된다. 레드존의 모든 바이트는 항상 같은 값을 가져야한다. 문제가 있는 경우 오브젝트 경계 다음(레드존)에 쓰기가 발생한다.

The Redzone following the object. The Redzone is used to detect writes after the object. All bytes should always have the same value. If there is any deviation then it is due to a write after the object boundary.


(Redzone 정보는 SLAB_RED_ZONE이 설정된 경우에만 사용할 수 있으며 slub_debug에서 이 옵션(Z)을 설정할 수 있다.)

(Redzone information is only available if SLAB_RED_ZONE is set. slub_debug sets that option)


Padding <address> : <bytes>

사용되지 않은 데이터로 공간을 채워서 다음 오브젝트를 올바르게 정렬시킨다. 디버깅의 경우 적어도 4 바이트의 패딩이 있는지 확인한다. 이렇게 하면 슬랩 오브젝트를 넘어선 쓰기를 탐지 할 수 있다.

Unused data to fill up the space in order to get the next object properly aligned. In the debug case we make sure that there are at least 4 bytes of padding. This allows the detection of writes before the object.


3.스택 덤프(A stackdump) 

스택 덤프는 에러가 발견된 함수 위치를 나타낸다. 오염을 일으킨 원인은 오브젝트를 할당하거나 해제 한 함수를 살펴보면 더 쉽게 발견 될 수 있다.

The stackdump describes the location where the error was detected. The cause of the corruption is may be more likely found by looking at the function that allocated or freed the object.


4.시스템의 지속적인 작동을 보장하기 위해 문제가 어떻게 처리되었는지에 대한 출력(Report on how the problem was dealt with in order to ensure the continued operation of the system.)

이 로그들은 아래와 같은 로그와 함께 출력된다.

These are messages in the system log beginning with


FIX <slab cache affected>: <corrective action taken>

위 샘플에서 SLUB은 활성 오브젝트의 레드존이 덮어쓰기 된것을 발견했다. 8 자 길이의 슬랩에 8 자의 문자열이 기록되었다. 그러나 8 자 문자열은 0으로 종료되어야 한다. 0은 레드존의 첫 번째 바이트를 덮어 쓴다. 문제의 세부 사항을 출력한 후 FIX SLUB 메시지는 SLUB이 레드존을 적절한 값으로 복원 한 다음 시스템 작동이 계속된다는 것을 알려준다.

In the above sample SLUB found that the Redzone of an active object has been overwritten. Here a string of 8 characters was written into a slab that has the length of 8 characters. However, a 8 character string needs a terminating 0. That zero has overwritten the first byte of the Redzone field. After reporting the details of the issue encountered the FIX SLUB message tells us that SLUB has restored the Redzone to its proper value and then system operations continue.


비상시의 동작(Emergency operations)

부팅시 최소한의 디버깅만(sanity 체크)을 활성화 할 수 있다.

Minimal debugging (sanity checks alone) can be enabled by booting with


slub_debug=F

일반적으로 이 설정은 만약 잘못된 커널 코드에 의해 corrupting 오브젝트가 유지되더라도 시스템이 계속 동작하게 해주는 slub의 복원기능을 활성화하는데 충분하다.이는 프로덕션 시스템에서 중요 할 수 있다. 성능은 sanity 검사의 영향을 받을 것이고 syslog에 오류 메시지가 계속해서 발생하지만 전체 디버깅과 달리 추가 메모리는 사용되지 않는다.

This will be generally be enough to enable the resiliency features of slub which will keep the system running even if a bad kernel component will keep corrupting objects. This may be important for production systems. Performance will be impacted by the sanity checks and there will be a continual stream of error messages to the syslog but no additional memory will be used (unlike full debugging).


보장되는 것은 없다. 잘못된 커널 코드는 여전히 수정해야 한다. 오염된 적이 있던 슬랩을 찾고 해당 캐시에 대해서만 디버깅을 활성화하여 성능을 더욱 최적화 할 수 있다.

No guarantees. The kernel component still needs to be fixed. Performance may be optimized further by locating the slab that experiences corruption and enabling debugging only for that cache


예를 들어(I.e),


 slub_debug=F,dentry

오브젝트의 영역을 벗어난 곳에 대한 쓰기접근에 의해 오염이 발생하면 다른 오브젝트의 시작 부분이 오염되지 않도록 레드존을 활성화하는 것이 좋다.

If the corruption occurs by writing after the end of the object then it may be advisable to enable a Redzone to avoid corrupting the beginning of other objects.


slub_debug=FZ,dentry

slabinfo의 확장모드와 플로팅(Extended slabinfo mode and plotting)

slabinfo 툴은 아래를 포함한 특별한 extended mode를 지원한다.

The slabinfo tool has a special ‘extended’ (‘-X’) mode that includes:


총 슬랩캐시(Slabcache Totals)

사이즈로 정렬한 슬랩리스트(Slabs sorted by size (up to -N <num> slabs, default 1))

loss를 기준으로 정렬할 슬랩리스트(Slabs sorted by loss (up to -N <num> slabs, default 1))


또한 이 모드에서 slabinfo는 동적으로 크기(G/M/K)를 조정하지 않고 모든 것을 바이트 단위로 보고한다(이 기능은 ‘-B’옵션을 통해 다른 slabinfo 모드에서도 사용할 수 있음). 이 것은 출력 내용을 더욱 더 정밀하고 정확하게 만들어준다. 또한 어떤 의미에서 ‘-X’ 모드는 slabinfo-gnuplot.sh 스크립트를 사용하여 출력을 plot 할 수 있기 때문에 슬랩의 동작 분석을 단순하게 한다. 따라서 숫자(많은)를 통해 분석하는 것에서 좀 더 쉬운 시각적은 분석을 하게 한다.

Additionally, in this mode slabinfo does not dynamically scale sizes (G/M/K) and reports everything in bytes (this functionality is also available to other slabinfo modes via ‘-B’ option) which makes reporting more precise and accurate. Moreover, in some sense the `-X’ mode also simplifies the analysis of slabs’ behaviour, because its output can be plotted using the slabinfo-gnuplot.sh script. So it pushes the analysis from looking through the numbers (tons of numbers) to something easier — visual analysis.


plot을 만들기 위해서는(To generate plots):


a) sliabinfo의 extended record를 수집한다, 예를 들면 아래와 같다.(collect slabinfo extended records, for example)


while [ 1 ]; do slabinfo -X >> FOO_STATS; sleep 1; done

b) stats 파일을 slabinfo-gnuplot.sh 스크립트에 전달한다(pass stats file(-s) to slabinfo-gnuplot.sh script)


slabinfo-gnuplot.sh FOO_STATS [FOO_STATS2 .. FOO_STATSN]

slabinfo-gnuplot.sh 스크립트는 수집 된 레코드를 사전 처리하고 STATS 파일 당 아래 3 개 png 파일 (3 개의 사전 처리 캐시 파일)을 생성한다.

The slabinfo-gnuplot.sh script will pre-processes the collected records and generates 3 png files (and 3 pre-processing cache files) per STATS file:


Slabcache Totals: FOO_STATS-totals.png

Slabs sorted by size: FOO_STATS-slabs-by-size.png

Slabs sorted by loss: FOO_STATS-slabs-by-loss.png

slabinfo-gnuplot 스크릅트가 유용 할 수있는 또 다른 사용 사례는 일부 코드수정에 대한 슬랩 동작을 “이전”과 “후”로 비교해야 할 때이다. 거기서 당신을 도울 수 있도록 slabinfo-gnuplot.sh 스크립트는 다른 측정의 Slabcache Totals 섹션을 ‘병합’할 수 있다. 시각적으로 N 개의 그림을 비교하려면

Another use case, when slabinfo-gnuplot can be useful, is when you need to compare slabs’ behaviour “prior to” and “after” some code modification. To help you out there, slabinfo-gnuplot.sh script can ‘merge’ the `Slabcache Totals` sections from different measurements. To visually compare N plots:


a) 필요한만큼 STATS1, STATS2, .. STATSN 파일을 수집하기

a) Collect as many STATS1, STATS2, .. STATSN files as you need


while [ 1 ]; do slabinfo -X >> STATS<X>; sleep 1; done

b) 해당 STATS 파일을 사전 처리하기

b) Pre-process those STATS files


slabinfo-gnuplot.sh STATS1 STATS2 .. STATSN

c) ‘-t’모드에서 slabinfo-gnuplot.sh를 실행하여 생성 된 모든 전처리 된 *-totals를 전달

c) Execute slabinfo-gnuplot.sh in ‘-t’ mode, passing all of the generated pre-processed *-totals


slabinfo-gnuplot.sh -t STATS1-totals STATS2-totals .. STATSN-totals

이 절차는 단일 플롯(png 파일)을 생성한다.

This will produce a single plot (png file).


플롯은 커질 수 있으므로 약간의 변동이나 작은 스파이크가 눈에 띄지 않을 수 있다. 이를 처리하기 위해 slabinfo-gnuplot.sh 스크립트에는 ‘zoom-in’/’zoom-out’ 두 가지 옵션이 있다 :

Plots, expectedly, can be large so some fluctuations or small spikes can go unnoticed. To deal with that, `slabinfo-gnuplot.sh’ has two options to ‘zoom-in’/’zoom-out’:


a) -s %d,%d 는 기본 이미지 너비와 높이를 덮어쓴다.

a) -s %d,%d overwrites the default image width and heigh


b) -r %d,%d 는 사용할 샘플의 범위를 명시한다(예를 들어, slabinfo -X >> FOOT_STATS; sleep 1; case에서 “-r 40,60” 을 사용하면 40번초~60초 사이에 수집된 샘플만 플롯된다.

b) -r %d,%d specifies a range of samples to use (for example, in `slabinfo -X >> FOO_STATS; sleep 1;’ case, using a “-r 40,60” range will plot only samples collected between 40th and 60th seconds).


Christoph Lameter, May 30, 2007

Sergey Senozhatsky, October 23, 2015

이 문서에서는 새로운 리눅스 스케쥴러에서 개정된 nice-level 구현에 대한 개념을 설명한다. nice level은 리눅스에서 꽤 취약한 부분이였고 사람들은 지속적으로 nice +19태스크가 기존보다 cpu time을 덜 쓰게 만들어 달라고 커널개발자들을 괴롭혔다.

This document explains the thinking about the revamped and streamlined nice-levels implementation in the new Linux scheduler. Nice levels were always pretty weak under Linux and people continuously pestered us to make nice +19 tasks use up much less CPU time.


유감스럽게도 기존 스케쥴러에서 이런 요구사항을 구현하는게 쉽지않았다(그렇지 않았다면 진작 구현했을것이다) 왜냐하면 nice level은 전통적으로 타임슬라이스 길이와 엮여있고 타임슬라이스의 단위는 HZ tick에 의해 정해지기 때문이다. 제일 작은 타임슬라이스는 1/HZ이다.

Unfortunately that was not that easy to implement under the old scheduler, (otherwise we'd have done it long ago) because nice level support was historically coupled to timeslice length, and timeslice units were driven by the HZ tick, so the smallest timeslice was 1/HZ.


(2003년도에 ) O(1) 스케쥴러에서 음수 값의 nice는 2.4 이전에 비해 더욱 강력하게 변했다. (사람들은 이런 변화에 기뻐했다.) 또한 의도적으로 linear timeslice rule을 조정했다.  따라서 nice 19 레벨은 정확히 1 jiffies를 갖게 되었다. 이해를 돕기위한 타임슬라이스 그래프는 아래와 같다.

In the O(1) scheduler (in 2003) we changed negative nice levels to be much stronger than they were before in 2.4 (and people were happy about that change), and we also intentionally calibrated the linear timeslice rule so that nice +19 level would be _exactly_ 1 jiffy. To better understand it, the timeslice graph went like this (cheesy ASCII art  alert!):


따라서 만약 누군가가 태스크의 nice level을 바꾼다면, 현재의 nice level +19는 기존보다 더 많은 CPU시간을 줄것이다(우선순위를 확장하기 위해 ABI를 변경하는 방식의 해결책은 일찍이 폐기되었다). 

So that if someone wanted to really renice tasks, +19 would give a much bigger hit than the normal linear rule would do. (The solution of changing the ABI to extend priorities was discarded early on.) 


이런 방식은 어느정도 시간이 걸렸지만 HZ가 1000일때 1 jiffies는 1ms이 되므로, CPU시간 0.1%를 쓸 수 있음을 의미한다. 이것은 조금 극단적이라고 느껴지지만 0.1%는 매우 적은 CPU시간이기 때문에 극단적이지 않다. 하지만 너무 적은 시간동안 CPU를 사용하면 잦은 재스케쥴링(1ms마다)으로 이어진다.(그리고 cache trash 등의 문제가 있다. HW사양이 낮고 cache가 작던 시절은 지나갔고 사람들은 여러개의 crunching app을 nice +19에서 수행한다는 것을 기억하라)

This approach worked to some degree for some time, but later on with HZ=1000 it caused 1 jiffy to be 1 msec, which meant 0.1% CPU usage which we felt to be a bit excessive. Excessive _not_ because it's too small of a CPU utilization, but because it causes too frequent (once per millisec) rescheduling. (and would thus trash the cache, etc. Remember, this was long ago when ha


이 문서에서는 새로운 리눅스 스케쥴러에서 개정된 nice-level 구현에 대한 개념을 설명한다. nice level은 리눅스에서 꽤 취약한 부분이였고 사람들은 지속적으로 nice +19태스크가 기존보다 cpu time을 덜 쓰게 만들어 달라고 커널개발자들을 괴롭혔다.

This document explains the thinking about the revamped and streamlined nice-levels implementation in the new Linux scheduler. Nice levels were always pretty weak under Linux and people continuously pestered us to make nice +19 tasks use up much less CPU time.


유감스럽게도 기존 스케쥴러에서 이런 요구사항을 구현하는게 쉽지않았다(그렇지 않았다면 진작 구현했을것이다) 왜냐하면 nice level은 전통적으로 타임슬라이스 길이와 엮여있고 타임슬라이스의 단위는 HZ tick에 의해 정해지기 때문이다. 제일 작은 타임슬라이스는 1/HZ이다.

Unfortunately that was not that easy to implement under the old scheduler, (otherwise we'd have done it long ago) because nice level support was historically coupled to timeslice length, and timeslice units were driven by the HZ tick, so the smallest timeslice was 1/HZ.


(2003년도에 ) O(1) 스케쥴러에서 음수 값의 nice는 2.4 이전에 비해 더욱 강력하게 변했다. (사람들은 이런 변화에 기뻐했다.) 또한 의도적으로 linear timeslice rule을 조정했다.  따라서 nice 19 레벨은 정확히 1 jiffies를 갖게 되었다. 이해를 돕기위한 타임슬라이스 그래프는 아래와 같다.

In the O(1) scheduler (in 2003) we changed negative nice levels to be much stronger than they were before in 2.4 (and people were happy about that change), and we also intentionally calibrated the linear timeslice rule so that nice +19 level would be _exactly_ 1 jiffy. To better understand it, the timeslice graph went like this (cheesy ASCII art  alert!):


따라서 만약 누군가가 태스크의 nice level을 바꾼다면, 현재의 nice level +19는 기존보다 더 많은 CPU시간을 줄것이다(우선순위를 확장하기 위해 ABI를 변경하는 방식의 해결책은 일찍이 폐기되었다). 

So that if someone wanted to really renice tasks, +19 would give a much bigger hit than the normal linear rule would do. (The solution of changing the ABI to extend priorities was discarded early on.) 


이런 방식은 어느정도 시간이 걸렸지만 HZ가 1000일때 1 jiffies는 1ms이 되므로, CPU시간 0.1%를 쓸 수 있음을 의미한다. 이것은 조금 극단적이라고 느껴지지만 0.1%는 매우 적은 CPU시간이기 때문에 극단적이지 않다. 하지만 너무 적은 시간동안 CPU를 사용하면 잦은 재스케쥴링(1ms마다)으로 이어진다.(그리고 cache trash 등의 문제가 있다. HW사양이 낮고 cache가 작던 시절은 지나갔고 사람들은 여러개의 crunching app을 nice +19에서 수행한다는 것을 기억하라)

This approach worked to some degree for some time, but later on with HZ=1000 it caused 1 jiffy to be 1 msec, which meant 0.1%


이 문서에서는 새로운 리눅스 스케쥴러에서 개정된 nice-level 구현에 대한 개념을 설명한다. nice level은 리눅스에서 꽤 취약한 부분이였고 사람들은 지속적으로 nice +19태스크가 기존보다 cpu time을 덜 쓰게 만들어 달라고 커널개발자들을 괴롭혔다.

This document explains the thinking about the revamped and streamlined nice-levels implementation in the new Linux scheduler. Nice levels were always pretty weak under Linux and people continuously pestered us to make nice +19 tasks use up much less CPU time.


유감스럽게도 기존 스케쥴러에서 이런 요구사항을 구현하는게 쉽지않았다(그렇지 않았다면 진작 구현했을것이다) 왜냐하면 nice level은 전통적으로 타임슬라이스 길이와 엮여있고 타임슬라이스의 단위는 HZ tick에 의해 정해지기 때문이다. 제일 작은 타임슬라이스는 1/HZ이다.

Unfortunately that was not that easy to implement under the old scheduler, (otherwise we'd have done it long ago) because nice level support was historically coupled to timeslice length, and timeslice units were driven by the HZ tick, so the smallest timeslice was 1/HZ.


(2003년도에 ) O(1) 스케쥴러에서 음수 값의 nice는 2.4 이전에 비해 더욱 강력하게 변했다. (사람들은 이런 변화에 기뻐했다.) 또한 의도적으로 linear timeslice rule을 조정했다.  따라서 nice 19 레벨은 정확히 1 jiffies를 갖게 되었다. 이해를 돕기위한 타임슬라이스 그래프는 아래와 같다.

In the O(1) scheduler (in 2003) we changed negative nice levels to be much stronger than they were before in 2.4 (and people were happy about that change), and we also intentionally calibrated the linear timeslice rule so that nice +19 level would be _exactly_ 1 jiffy. To better understand it, the timeslice graph went like this (cheesy ASCII art  alert!):


따라서 만약 누군가가 태스크의 nice level을 바꾼다면, 현재의 nice level +19는 기존보다 더 많은 CPU시간을 줄것이다(우선순위를 확장하기 위해 ABI를 변경하는 방식의 해결책은 일찍이 폐기되었다). 

So that if someone wanted to really renice tasks, +19 would give a much bigger hit than the normal linear rule would do. (The solution of changing the ABI to extend priorities was discarded early on.) 


이런 방식은 어느정도 시간이 걸렸지만 HZ가 1000일때 1 jiffies는 1ms이 되므로, CPU시간 0.1%를 쓸 수 있음을 의미한다. 이것은 조금 극단적이라고 느껴지지만 0.1%는 매우 적은 CPU시간이기 때문에 극단적이지 않다. 하지만 너무 적은 시간동안 CPU를 사용하면 잦은 재스케쥴링(1ms마다)으로 이어진다.(그리고 cache trash 등의 문제가 있다. HW사양이 낮고 cache가 작던 시절은 지나갔고 사람들은 여러개의 crunching app을 nice +19에서 수행한다는 것을 기억하라)

This approach worked to some degree for some time, but later on with HZ=1000 it caused 1 jiffy to be 1 msec, which meant0.1% CPU usage which we felt to be a bit excessive. Excessive _not_ because it's too small of a CPU utilization, but because it causes too frequent (once per millisec) rescheduling. (and would thus trash the cache, etc. Remember, this was long ago when hardware was weaker and caches were smaller, and people were running number crunching apps at nice +19.)


따라서 HZ가 1000일 때 우리는 nice +19가 5ms를 사용하도록 바꿨다.  우리는 이 값이 적절한 minimal granularity라고 느꼈다. 그리고 이 값은 5%의 CPU사용시간으로 환산된다. 하지만 HZ 변화에 민감한 것은 여전했다.  그리고 nice +19이 너무 적게 cpu를 사용한다는 불만은 없었고 여전히 너무 많이 사용한다는 불만이 있다.

So for HZ=1000 we changed nice +19 to 5msecs, because that felt like the right minimal granularity - and this translates to 5% CPU utilization. But the fundamental HZ-sensitive property for nice+19 still remained, and we never got a single complaint about nice +19 being too _weak_ in terms of CPU utilization, we only got complaints about it (still) being too _strong_ :-)


요약하자면, 우리는 nice level이 늘 일관성을 유지하기를 원한다. 하지만 HZ와 jiffies의 제약, timeslice와 granularity와 연결되어 있는 그들의 좋지 않은 디자인레벨 안에서는 어려워 보였다.

To sum it up: we always wanted to make nice levels more consistent, but within the constraints of HZ and jiffies and their nasty design level coupling to timeslices and granularity it was not really viable.


리눅스의 nice level 지원에 대한 두번째 불만(빈번하진 않지만 여전히 주기적으로 발생하는)은 nice level의 비대칭성이다(위에 그려진 그림에서 볼 수 있다). 아니면 좀 더 정확해지길 원한다. 기본적으로 nice API는 상대적인 것에 비해 nice level의 양상은 절대적으로 nice level 값 자체에 의존적이다.

The second (less frequent but still periodically occurring) complaint about Linux's nice level support was its assymetry around the origo (which you can see demonstrated in the picture above), or more accurately: the fact that nice level behavior depended on the _absolute_  nice level as well, while the nice API itself is fundamentally "relative":

int nice(int inc)

asmlinkage long sys_nice(int increment)

첫번째는 glibc API, 두번째는 syscall API이다. inc는 현재 nice level이 무엇이냐에 따라 상대적이라는것에 주의해야 한다. bash의 nice 커맨드같은 것이 이런 상대적인 API를 그대로 따르고 있다.

(the first one is the glibc API, the second one is the syscall API.) Note that the 'inc' is relative to the current nice level. Tools like bash's "nice" command mirror this relative API.


예전 스케줄러에서는, 예를 들어 nice level이 1인 태스크와 nice level이 2인 다른 태스크가 실행되면 두 태스크간의 cpu 분배는 부모 쉘의 nice level에 의존적이였다. 부모 쉘의  nice level이 -10일 때의 cpu할당량은 nice +5나 nice +10때와는 다르다.

With the old scheduler, if you for example started a niced task with +1 and another task with +2, the CPU split between the two tasks would depend on the nice level of the parent shell - if it was at nice -10 the CPU split was different than if it was at +5 or +10.


리눅스의 nice level 지원에 대한 세번째 불만은  음수의 nice level이 충분히 효과적이지 않다는 것이다. 따라서 많은 사람들이 audio app을 실행할 때 SCHED_FIFO같은 실시간 우선순위를 사용한다. 하지만 이 경우 다른 문제가 생길 수 있다: SCHED_FIFO는 starvation 문제에서 자유롭지 못하고 검증되지 않은 SCHED_FIFO 어플리케이션은 시스템을 lock-up상태로 만들 수 있다.

A third complaint against Linux's nice level support was that negative nice levels were not 'punchy enough', so lots of people had to resort to run audio (and other multimedia) apps under RT priorities such as SCHED_FIFO. But this caused other problems: SCHED_FIFO is not starvation proof, and a buggy SCHED_FIFO app can also lock up the system for good.


리눅스 커널 v2.6.23의 새로운 스케쥴러에서는  이런 세가지 종류의 불만을 해결했다:

The new scheduler in v2.6.23 addresses all three types of complaints:


첫번째 컴플레인(nice level이 충분히 효과적이지 않은 문제)을 해결하기 위해, 스케줄러는 timeslice와 HZ개념과의 연결을 끊었다(그리고 granulariry는 nice level과 구별되는 개념으로 바뀌었다).  따라서 더욱 일관된 nice +19 지원의 구현이 가능해졌다: 기존 스케줄러가 3~5~9%의 범위에서 변화가 있던것과 달리 새로운 스케줄러에서 nice +19 태스크들은 HZ에 상관없이 1.5%를 얻을 수 있다.

To address the first complaint (of nice levels being not "punchy" enough), the scheduler was decoupled from 'time slice' and HZ concepts (and granularity was made a separate concept from nice levels) and thus it was possible to implement better and more consistent nice +19 support: with the new scheduler nice +19 tasks get a HZ-independent 1.5%, instead of the variable 3%-5%-9% range they got in the old scheduler.


두번째 불만(nice level이 일관되지 않다는 문제)을 해결하기 위해, 새로운 스케줄러는 nice(1)가 그들의 절대적인 nice level과 상관없이 태스크들에게 동일한 CPU 사용량 변화를 주도록 만들었다. 따라서 새로운 스케줄러에서는, nice +10과 nice +11로 동작하는 태스크 사이의 CPU사용량 분배와 nice -5 와 nice -4로 동작하는 태스크 사이의 CPU 사용량 분배는 동일하다(하나는 55%의 CPU시간을, 나머지는 45%의 시간을 얻는다). 

To address the second complaint (of nice levels not being consistent), the new scheduler makes nice(1) have the same CPU utilization effect on tasks, regardless of their absolute nice levels. So on the new scheduler, running a nice +10 and a nice 11 task has the same CPU utilization "split" between them as running a nice -5 and a nice -4 task. (one will get 55% of the CPU, the other 45%.)


이것을 구현하기 위해 nice level이 multiplicative(or exponential)하게 바꼈다. 이 방식대로라며 어떤 nice level에서 시작하던지 상관없이 결과는 항상 동일하다.

That is why nice levels were changed to be "multiplicative" (or exponential) - that way it does not matter which nice level you start out from, the 'relative result' will always be the same.


세번째 불만(음수의 nice level이 충분히 효과적이지 않고 audio app들이 더욱 위험한 SCHED_FIFO  스케쥴링 정책으로 수행하게 하는 문제)은 새로운 스케줄러에 의해 거의 자동으로 해결되었다. 음수의 nice level을 더욱 강하게 만드는 것은 dynamic range of nice level을 재조정하는것으로 해결된다.

The third complaint (of negative nice levels not being "punchy" enough and forcing audio apps to run under the more dangerous SCHED_FIFO scheduling policy) is addressed by the new scheduler almost automatically: stronger negative nice levels are an automatic side-effect of the recalibrated dynamic range of nice levels.

schedstats version 15에서는 sched_yield의 yld_exp_empty, yld_act_empty, yld_both_empty 카운터를 제거했다. 그 외의 내용은 버전 14와 같다.

Version 15 of schedstats dropped counters for some sched_yield: yld_exp_empty, yld_act_empty and yld_both_empty. Otherwise, it is identical to version 14.


schedstats 버전 14에는 kernel 2.6.20에 추가된 sched_domains에 대한 지원이 추가됐다.  나머지 내용은 kernel 2.6.13 - 2.6.19 kernel에 있었던 version 12와 동일하다.(버전13은 커널에 릴리즈되지 않았다). 어떤 카운터는 런큐마다 존재하는게 더 적합하고 나머지는 도메인마다 존재하는게 적합하다. 또한 도메인과 관련된 정보는 CONFIG_SMP를 사용하는 머신에서만 사용가능하다.

Version 14 of schedstats includes support for sched_domains, which hit the mainline kernel in 2.6.20 although it is identical to the stats from version 12 which was in the kernel from 2.6.13-2.6.19 (version 13 never saw a kernel release). Some counters make more sense to be per-runqueue; other to be per-domain. Note that domains (and their associated information) will only be pertinent and available on machines utilizing CONFIG_SMP.


schedstat 버전 14에서는, 각 cpu 마다 최소 한 레벨의 도메인 통계가 존재한다. 그리고 (아마도) 하나 이상의 도메인이 존재할 것이다. 현재 구현에서 도메인이 별도의 이름을 가지지는 않는다. domain0가 가장 도메인에 tightly focused되어있는 반면에 제일 높은 번호의 도메인은 보통 머신의 모든 CPU간의 로드밸런싱을 중재한다. 때때로 로드밸런싱은 오직 cpu pair들 사이에서만 발생한다. 현재 3레벨 이상의 도메인을 가진 아키텍쳐는 존재하지 않는다. 도메인 통계의 첫 필드는 해당 도메인에 속해 있는 cpu목록을 나타내는 bitmap이다.

In version 14 of schedstat, there is at least one level of domain statistics for each cpu listed, and there may well be more than one domain. Domains have no particular names in this implementation, but the highest numbered one typically arbitrates balancing across all the cpus on the machine, while domain0 is the most tightly focused domain, sometimes balancing only between pairs of cpus. At this time, there are no architectures which need more than three domain levels. The first field in the domain stats is a bit map indicating which cpus are affected by that domain.


필드들은 모두 값이 증가하기만 하는 카운터이다. 이 카운터를 사용할 프로그램들은 특정 값에서 관측을 시작하고 카운터가 얼마나 증가했는지를 매 관측시마다 계산해야 한다. 이런식으로 많은 카운터에 대해 동작하는 펄 스크립트는 아래 경로에서 찾을 수 있다.

These fields are counters, and only increment. Programs which make use of these will need to start with a baseline observation and then calculate the change in the counters at each subsequent observation. A perl script which does this for many of the fields is available at


http://eaglet.rain.com/rick/linux/schedstat/

버전이 바뀌는 주 원인은 출력 형태의 변화이기 때문에 이런 류의 스크립트는 version-specific할 필요가 있다. 버전별 스크립트를 작성하기 위해, 각 필드들에 대한 설명을 여기에 실었다.

Note that any such script will necessarily be version-specific, as the main reason to change versions is changes in the output format. For those wishing to write their own scripts, the fields are described here.


CPU 통계정보(CPU statistics)

cpu<N> 1 2 3 4 5 6 7 8 9

첫 필드는 sched_yield()의 통계이다.

1) # sched_yield()가 호출된 횟수

First field is a sched_yield() statistic:

1) # of times sched_yield() was called


다음 세 필드는 schedule()의 통계이다.

2) O(1) 스케쥴러가 사용했던 legacy array expiration count횟수,  ABI 호환을 위해 유지하고 있지만 항상 0으로 설정되어 있음

3) schedule()이 호출된 횟수

4) cpu를 idle 상태에서 깨우면서 schedule()이 호출된 횟수

Next three are schedule() statistics:

2) This field is a legacy array expiration count field used in the O(1) scheduler. We kept it for ABI compatibility, but it is always set to zero.

3) # of times schedule() was called

4) # of times schedule() left the processor idle


다음 두 필드는 try_to_wake_up()에 대한 통계이다.

5) try_to_wake_up()이 호출된 횟수

6) try_to_wake_up()이 local cpu를 깨우기 위해 호출된 횟수

Next two are try_to_wake_up() statistics:

5) # of times try_to_wake_up() was called

6) # of times try_to_wake_up() was called to wake up the local cpu


다음 세 필드는 scheduling latency에 대한 통계이다:

7) 이 cpu에서 태스크를 실행한 모든 시간의 합(jiffies단위)

8) 이 cpu에서 태스크가 실행되기 위해 기다린 모든 시간의 합(jiffies단위)

9) 이 cpu에서 사용된 타임슬라이스의 합

Next three are statistics describing scheduling latency:

7) sum of all time spent running by tasks on this processor (in jiffies)

8) sum of all time spent waiting to run by tasks on this processor (in

jiffies)

9) # of timeslices run on this cpu


도메인 통계정보(Domain statistics)

아래 필드들은 CPU가 속한 도메인마다 하나씩 생성된다. 만약 CONFIG_SMP가 정의되어 있지 않다면, 도메인은 사용되지 않고 이 정보는 결과에 보이지 않는다.

One of these is produced per domain for each cpu described. (Note that if CONFIG_SMP is not defined, *no* domains are utilized and these lines will not appear in the output.)


domain<N> <cpumask> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36

첫 필드는 이 도메인에서 동작하는 cpu 정보를 나타내는 비트맵이다. 다음 24개 필드는 load_balance() 통계에 대해서 idle 타입(idle, busy, newly idle)에 따라 나누어 나타낸다.

The first field is a bit mask indicating what cpus this domain operates over. The next 24 are a variety of load_balance() statistics in grouped into types  of idleness (idle, busy, and newly idle):


CPU가 idle 상태일 때의 통계정보

1) 이 도메인에 대해 load_balance()가 불린 횟수

2) 이 도메인에 대해 load_balance()가 불렸지만 로드밸런싱이 필요하지 않은 상황에 대한 횟수

3) 이 도메인에 대해 load_balance()가 불렸고 태스트 이동을 시도했으나 실패한 상황에 대한 횟수

4) 이 도메인에 대해 load_balance()가 매번 불렸을 때의 로드 불균형의 총합

5) 이 도메인에 대해 pull_task()가 호출된 횟수

6) 이 도메인에 대해 pull_task()가 불렸지만 태스크가 cache-hot이였던 횟수

7) 이 도메인에 대해 load_balance()가 불렸지만 바쁜 런큐를 찾지 못했던 횟수

8) 이 도메인에 대해 바쁜 런큐는 찾았지만 바쁜 그룹은 못 찾은 횟수

1) # of times in this domain load_balance() was called when the cpu was idle

2) # of times in this domain load_balance() checked but found the load did not require balancing when the cpu was idle

3) # of times in this domain load_balance() tried to move one or more tasks and failed, when the cpu was idle

4) sum of imbalances discovered (if any) with each call to load_balance() in this domain when the cpu was idle

5) # of times in this domain pull_task() was called when the cpu was idle

6) # of times in this domain pull_task() was called even though the target task was cache-hot when idle

7) # of times in this domain load_balance() was called but did not find a busier queue while the cpu was idle

8) # of times in this domain a busier queue was found while the cpu was idle but no busier group was found


CPU가 busy 상태일 때의 통계정보

9) 이 도메인에 대해 load_balance()가 불린 횟수

10) 이 도메인에 대해 load_balance()가 불렸지만 로드벨런싱이 필요하지 않은 상황에 대한 횟수

11) 이 도메인에 대해 load_balance()가 불렸고 태스트이동을 시도했으나 실패한 상황에 대한 횟수

12) 이 도메인에 대해 load_balance()가 매번 불렸을 때의 로드 불균형의 총합

13) 이 도메인에 대해 pull_task()가 호출된 횟수

14) 이 도메인에 대해 pull_task()가 불렸지만 태스크가 cache-hot이였던 횟수

15) 이 도메인에 대해 load_balance()가 불렸지만 바쁜 런큐를 찾지 못했던 횟수

16) 이 도메인에 대해 바쁜 런큐는 찾았지만 바쁜 그룹은 못 찾은 횟수

9) # of times in this domain load_balance() was called when the cpu was busy

10) # of times in this domain load_balance() checked but found the load did not require balancing when busy

11) # of times in this domain load_balance() tried to move one or more tasks and failed, when the cpu was busy

12) sum of imbalances discovered (if any) with each call to load_balance() in this domain when the cpu was busy

13) # of times in this domain pull_task() was called when busy

14) # of times in this domain pull_task() was called even though the target task was cache-hot when busy

15) # of times in this domain load_balance() was called but did not find a busier queue while the cpu was busy

16) # of times in this domain a busier queue was found while the cpu was busy but no busier group was found


CPU가 newly idle 상태일 때의 통계정보

17) 이 도메인에 대해 load_balance()가 불린 횟수

18) 이 도메인에 대해 load_balance()가 불렸지만 로드밸런싱이 필요하지 않은 상황에 대한 횟수

19) 이 도메인에 대해 load_balance()가 불렸고 태스트이동을 시도했으나 실패한 상황에 대한 횟수

20) 이 도메인에 대해 load_balance()가 매번 불렸을 때의 로드 불균형의 총합

21) 이 도메인에 대해 pull_task()가 호출된 횟수

22) 이 도메인에 대해 pull_task()가 불렸지만 태스크가 cache-hot이였던 횟수

23) 이 도메인에 대해 load_balance()가 불렸지만 바쁜 런큐를 찾지 못했던 횟수

24) 이 도메인에 대해 바쁜 런큐는 찾았지만 바쁜 그룹은 못 찾은 횟수

17) # of times in this domain load_balance() was called when the cpu was just becoming idle

18) # of times in this domain load_balance() checked but found the load did not require balancing when the cpu was just becoming idle

19) # of times in this domain load_balance() tried to move one or more tasks and failed, when the cpu was just becoming idle

20) sum of imbalances discovered (if any) with each call to load_balance() in this domain when the cpu was just becoming idle

21) # of times in this domain pull_task() was called when newly idle

22) # of times in this domain pull_task() was called even though the target task was cache-hot when just becoming idle

23) # of times in this domain load_balance() was called but did not find a busier queue while the cpu was just becoming idle

24) # of times in this domain a busier queue was found while the cpu was just becoming idle but no busier group was found


다음 세 필드는 active_load_balance()에 대한 통계를 나타낸다

25) active_load_balance()가 호출된 횟수

26) active_load_balance()가 태스크 이동에 실패한 횟수

27) active_load_balance()가 태스트 이동에 성공한 횟수

Next three are active_load_balance() statistics:

25) # of times active_load_balance() was called

26) # of times active_load_balance() tried to move a task and failed

27) # of times active_load_balance() successfully moved a task


다음 세 필드는 sched_balance_exec()에 대한 통계를 나타낸다.

28) sbe_cnt, 사용되지 않음

29) sbe_balanced, 사용되지 않음

30) sbe_pushed, 사용되지 않음

Next three are sched_balance_exec() statistics:

28) sbe_cnt is not used

29) sbe_balanced is not used

30) sbe_pushed is not used


다음 세 필드는 sched_balance_fork()에 대한 통계이다.

31) sbf_cnt, 사용되지 않음

32) sbf_balanced, 사용되지 않음

33) sbf_pushed, 사용되지 않음

Next three are sched_balance_fork() statistics:

31) sbf_cnt is not used

32) sbf_balanced is not used

33) sbf_pushed is not used


다음 세 필드는 try_to_wake_up()에 대한 통계이다.

34) try_to_wake_up()이 도메인의 다른 cpu에서 마지막으로 실행했던 태스크를 깨운횟수

35) try_to_wake_up()이 cache-cold인 태스크를 깨어나는 다른 CPU에게 이동시킨 횟수

36) try_to_wake_up()이 passive balancing을 시작한 횟수

Next three are try_to_wake_up() statistics:

34) # of times in this domain try_to_wake_up() awoke a task that last ran on a different cpu in this domain

35) # of times in this domain try_to_wake_up() moved a task to the waking cpu because it was cache-cold on its own cpu anyway

36) # of times in this domain try_to_wake_up() started passive balancing


/proc/<pid>/schedstat


schedstats는 프로세스 단위의 스케쥴링 정보를 포함하기 위해 새롭게 /proc/<pid>/schedstat 파일도 추가했다. 이 파일에는 관련된 프로세스에 대한 세가지 필드가 있다.

1) cpu에서 보낸 시간의 합

2) 런큐에서 기다린 시간의 합

3) 현재 cpu에서 사용한 timeslice의 합

schedstats also adds a new /proc/<pid>/schedstat file to include some of the same information on a per-process level. There are three fields in this file correlating for that process to:

1) time spent on the cpu

2) time spent waiting on a runqueue

3) # of timeslices run on this cpu


특정 프로세스나 프로세스 그룹이 스케쥴러의 정책 아래에서 어떻게 공평하게 실행되는지를 알기 위해 이런 필드들을 사용하도록 프로그램을 쉽게 짤 수 있다. 이런 프로그램의 간단한 버전은 아래에서 참고할 수 있다.

A program could be easily written to make use of these extra fields to report on how well a particular process or set of processes is faring under the scheduler's policies. A simple version of such a program is available at


http://eaglet.rain.com/rick/linux/schedstat/v12/latency.c



CFS Scheduler



1. 개요(Overview)

CFS는 "완벽하게 공정한 스케쥴러"를 의미하는 Ingo Molnar에 의해 구현된 새로운 "데스크탑" 프로세스 스케줄러다. Linux 2.6.23에 통합되었으며 이전 바닐라 스케쥴러의 SCHED_OTHER  c코드를 대체한다.

CFS stands for "Completely Fair Scheduler," and is the new "desktop" process scheduler implemented by Ingo Molnar and merged in Linux 2.6.23. It is the replacement for the previous vanilla scheduler's SCHED_OTHER interactivity code.


CFS 디자인의 80%는 한줄로 요약할 수 있다:

80% of CFS's design can be summed up in a single sentence:


CFS는 실제 하드웨어 위에서 "이상적이고, 정밀한 멀티태스킹 CPU"를 모델링한다.

CFS basically models an "ideal, precise multi-tasking CPU" on real hardware.


100%의 physical power를 가지고 있는 "(존재하지 않지만) 이상적인 멀티태스킹 CPU"는  각 태스크를 병렬적으로 정확히 1/nr_running의 공평한 속도로 실행하는 CPU이다. 예를 들어, 두개의 태스크가 실행중이라면, 태스크들은 각각 50%의 physical power로 병렬실행한다.

"Ideal multi-tasking CPU" is a (non-existent :-)) CPU that has 100% physical power and which can run each task at precise equal speed, in parallel, each at 1/nr_running speed. For example: if there are 2 tasks running, then it runs each at 50% physical power --- i.e., actually in parallel.


실제 하드웨어에서는 한 순간에 하나의 태스크만 실행할 수 있기 때문에 virtual runtime이라는 개념을 도입했다. 태스크의 virtual runtime은 위에서 설명한 이상적인 멀티태스킹 CPU에서 태스크가 실행을 시작하기 위한 다음 timeslice가 언제인지를  나타낸다. 실제로 태스크의 virtual runtime은 전체 실행중인 태스크 개수로 normalize한 진짜 실행시간이다.

On real hardware, we can run only a single task at once, so we have to introduce the concept of "virtual runtime." The virtual runtime of a task specifies when its next timeslice would start execution on the ideal multi-tasking CPU described above. In practice, the virtual runtime of a task is its actual runtime normalized to the total number of running tasks.


2. 몇가지 자세한 구현사항(FEW IMPLEMENTATION DETAILS)

CFS에서 virtual runtime은 각 태스크의 p->se.vruntime(나노초 단위)으로 표현되고 추적된다. 이 방식을 사용하면 태스크의 정확히 타임스탬프를 추적할 수 있고 태스크가 가져야 할 "예상 CPU 시간"을 측정할 수 있다.

In CFS the virtual runtime is expressed and tracked via the per-task p->se.vruntime (nanosec-unit) value. This way, it's possible to accurately timestamp and measure the "expected CPU time" a task should have gotten.


[ small detail: 이상적인 하드웨어에서, 모든 태스크는 늘 동일한 p->se.vruntime 필드를 가진다. 예를 들어 태스크들은 동시에 실행되고 모든 태스크는 CPU시간를 공평하게 분배받는다.]

[ small detail: on "ideal" hardware, at any time all tasks would have the same p->se.vruntime value --- i.e., tasks would execute simultaneously and no task would ever get "out of balance" from the "ideal" share of CPU time. ]


CFS의 태스크 선택 로직은 p->se.vruntime 필드에 기반한다. 매우 간단하다. CFS는 항상 가장 작은 ->se.vruntime 필드를 가진 태스크(제일 적게 실행된 태스크)를 실행하려고 시도한다. 또한 가능한 한 이상적인 멀티태스킹 하드웨어에 가깝게 CPU 시간을 나누려고 한다.

CFS's task picking logic is based on this p->se.vruntime value and it is thus very simple: it always tries to run the task with the smallest p->se.vruntime value (i.e., the task which executed least so far). CFS always tries to split up CPU time between runnable tasks as close to "ideal multitasking hardware" as possible.


대부분의 남은 CFS의 디자인은 nice 레벨, 멀티프로세싱, sleeper를 알아내기 위한 다양한 변형 알고리즘과 같은  몇가지 추가 장치(add-on embellishment)과 함께 이 단순한 개념에 집중한다.

Most of the rest of CFS's design just falls out of this really simple concept, with a few add-on embellishments like nice levels, multiprocessing and various algorithm variants to recognize sleepers.


3. 레드블랙트리(THE RBTREE)

CFS의 디자인은 (과거의 것을 많이 사용하지 않는다는 의미에서)꽤 급진적이다. 런큐에 대한 오래된 자료구조를 사용하지 않고 다음 태스크 실행을 위한 타임라인을 만들기 위해  시간 순으로 정렬된 레드블랙 트리를 사용한다. 그리고 배열을 교체하는 구조가 없다.(이런 변화로 인해 기존의 바닐라스케쥴러와 RSDL/SD가 영향을 받는다)

CFS's design is quite radical: it does not use the old data structures for the runqueues, but it uses a time-ordered rbtree to build a "timeline" of future task execution, and thus has no "array switch" artifacts (by which both the previous vanilla scheduler and RSDL/SD are affected).


CFS는 또한 런큐 안의 모든 태스크 중에서 제일 작은 vruntime값을 추적하고 있는 rq->cfs.min_vruntime 필드를 유지한다. 이 값은 단방향으로 증가되는 값이다. 시스템에 의해 처리된 일의 총량은 min_vruntime에 의해 추적된다. 이 값은 새롭게 활성화된 entity를 가능한 한 레드블랙 트리의 왼쪽에 위치시키는데 사용된다.

CFS also maintains the rq->cfs.min_vruntime value, which is a monotonic increasing value tracking the smallest vruntime among all tasks in the runqueue. The total amount of work done by the system is tracked using min_vruntime; that value is used to place newly activated entities on the left side of the tree as much as possible.


런큐안에 동작중인 혹은 동작가능한 태스크의 개수는 rq->cfs.load 필드를 통해 집계되고 있다. 이 값은 런큐에 enqueue된 모든 태스크의 전체 weight이다.

The total number of running tasks in the runqueue is accounted through the rq->cfs.load value, which is the sum of the weights of the tasks queued on the runqueue.


CFS는 모든 태스크들이 p->se.vruntime 필드에 의해 정렬된 모든 태스크들이 있는 레드블랙 트리를 유지한다. CFS는 이 트리의 가장 왼쪽의 태스크를 선택하고 집중한다. 시스템이 계속 동작할 수록 실행된 태스크는 점점 트리의 오른쪽으로 이동할 것이다. 천천히 하지만 반드시 모든 태스크가 가장 왼쪽의 태스크가 되도록 기회를 준다. 따라서 정해진 시간안에 CPU를 얻을 수 있다.

CFS maintains a time-ordered rbtree, where all runnable tasks are sorted by the p->se.vruntime key. CFS picks the "leftmost" task from this tree and sticks to it. As the system progresses forwards, the executed tasks are put into the tree more and more to the right --- slowly but surely giving a chance for every task to become the "leftmost task" and thus get on the CPU within a deterministic amount of time.


요약하자면, CFS는 다음과 같이 동작한다. 태스크가 잠시 실행된 후 스케쥴될 때(혹은 스케쥴러 틱이 발생할 때) 태스크의 CPU사용량은 집계된다. 태스크가 실제 CPU를 사용해서 소모한 (작은)시간은 p->se.vruntime에 추가된다.

Summing up, CFS works like this: it runs a task a bit, and when the task schedules (or a scheduler tick happens) the task's CPU usage is "accounted for": the (small) time it just spent using the physical CPU is added to p->se.vruntime. 


p->se.vruntime이 충분히 커지면 다른 태스크는 CFS가 관리하는 레드블랙 트리(시간 순으로 정렬된)의 가장 왼쪽 태스크가 된다. (작은 양의 "granularity"를 더해줘서 leftmost 태스크가 CPU를 과하게 쓰지 않게 하고 cache trashing도 막는다.) 그런다음 새롭게 가장왼쪽에 오게 된 태스크에 의해 현재 태스크가 선점된다.(leftmost task가 CPU를 점유한다)

Once p->se.vruntime gets high enough so that another task becomes the "leftmost task" of the time-ordered rbtree it maintains (plus a small amount of "granularity" distance relative to the leftmost task so that we do not over-schedule tasks and trash the cache), then the new leftmost task is picked and the current task is preempted.




4. CFS의 몇가지 기능(SOME FEATURES OF CFS)

CFS는 나노초 단위의 통계를 사용하고 있으며 jiffies나 HZ 값에 의존적으로 동작하지 않는다.  따라서 예전 스케쥴러가 가지고 있던 timeslice라는 개념을 CFS 스케쥴러는 가지고 있지 않다.  그리고 그 어떤 휴리스틱도 사용하지 않는다. 그대신 단 하나의 central tunable을 가지고 있다(단 CONFIG_SCHED_DEBUG를 활성화해야 한다.)

CFS uses nanosecond granularity accounting and does not rely on any jiffies or other HZ detail. Thus the CFS scheduler has no notion of "timeslices" in the way the previous scheduler had, and has no heuristics whatsoever. There is only one central tunable (you have to switch on CONFIG_SCHED_DEBUG):


/proc/sys/kernel/sched_min_granularity_ns

이 값은 스케쥴러를 데스크탑용(낮은지연시간)에서  서버용(good batching)으로  튜닝하는데 사용할 수 있다. 이 변수는 기본적으로 데스크탑 워크로드에 적합하게 설정되어 있다. SCHED_BATCH 태스크도 역시 CFS 스케줄러 모듈에 의해 처리된다.

which can be used to tune the scheduler from "desktop" (i.e., low latencies) to "server" (i.e., good batching) workloads. It defaults to a setting suitable for desktop workloads. SCHED_BATCH is handled by the CFS scheduler module too.


이런 디자인으로 인해, CFS 스케쥴러는  스케쥴러의 휴리스틱에 사용되는 현존하는 어떠한 공격에도 정상동작하고 응답성에 영향을 받지 않으며 예측된 행동을 보여준다.

Due to its design, the CFS scheduler is not prone to any of the "attacks" that exist today against the heuristics of the stock scheduler: fiftyp.c, thud.c, chew.c, ring-test.c, massive_intr.c all work fine and do not impact interactivity and produce the expected behavior.


CFS 스케쥴러는 과거의 바닐라 스케쥴러에 비해 nice level 처리와 SCHED_BATCH 태스크처리가 더 좋아졌다. 두 타입의 워크로드는 더욱 더 분리된다.

The CFS scheduler has a much stronger handling of nice levels and SCHED_BATCH than the previous vanilla scheduler: both types of workloads are isolated much more aggressively.


SMP 로드밸런싱 코드도 재작성되고 검증되었다. 기존의 런큐 탐색을 고려한 코드는 로드밸런싱에서 빠졌으며 현재는 스케줄링 모듈을 순회하는 방식이 사용되고 있다.  로드밸런싱 코드는 결과적으로 매우 단순해졌다.

SMP load-balancing has been reworked/sanitized: the runqueue-walking assumptions are gone from the load-balancing code now, and iterators of the scheduling modules are used. The balancing code got quite a bit simpler as a result.


5. 스케쥴링 정책(Scheduling policies)

CFS는 세가지 스케쥴링정책을 구현했다:


- SCHED_NORMAL(전통적으로 SCHED_OTHER로 불림)일반 태스크를 처리하는데 사용되는 정책이다.

- SCHED_NORMAL (traditionally called SCHED_OTHER)The scheduling policy that is used for regular tasks.


- SCHED_BATCH : 

일반 태스크들처럼 자주 태스크가 선점되지 않고, 태스크가 비교적 오래 수행되며 캐시를 더욱 잘 활용하지만 응답성에 좋지 않다. 배치작업에 적합한 정책이다.

Does not preempt nearly as often as regular tasks would, thereby allowing tasks to run longer and make better use of caches but at the cost of interactivity. This is well suited for batch jobs.


- SCHED_IDLE : nice level +19인 태스크보다 우선순위가 떨어지는 정책이다. 우선순위가 역전되는 문제를 피하기 위한 idle timer scheduler는 아니다.

This is even weaker than nice 19,  but its not a true idle timer scheduler in order to avoid to get into priority inversion problems which would deadlock the machine.



SCHED_FIFO/_RR 은 sched/rt.c에 구현되어 있고 POSIX에 정해진 것에 맞게 작성되었다. util-linux-ng 2.13.1.1 의 chrt를 사용하면 SCHED_IDLE을 제외한 모든 정책으로 프로세스를 설정할 수 있다

SCHED_FIFO/_RR are implemented in sched/rt.c and are as specified by POSIX. The command chrt from util-linux-ng 2.13.1.1 can set all of these except SCHED_IDLE.


6. 스케줄링 클래스(SCHEDULING CLASSES)

새로운 CFS 스케줄러는 스케줄러 모듈의 확장 가능한 계층구조인 스케줄링 클래스를 도입하는 방식으로 디자인되었다. 이 모듈들은 스케줄링 정책으로 추상화되며 스케줄러 코어에 의해 처리된다.  스케줄러 코어는 특정 스케줄링 정책에 대한 가정을 한 코드는 가지고 있지 않는다.

The new CFS scheduler has been designed in such a way to introduce "Scheduling Classes," an extensible hierarchy of scheduler modules. These modules encapsulate scheduling policy details and are handled by the scheduler core without the core code assuming too much about them.


sched/fair.c 는 위에서 기술한 CFS 스케줄러를 구현했다.

sched/fair.c implements the CFS scheduler described above.


sched/rt.c는 과거 바닐라 스케줄러가 했던 것에 비해 SCHED_FIFO와 SCHED_RR semantics를  간단한 방법으로 구현했다. 100개의 런큐를 사용하고(과거의 스케줄러와 달리 100개 모두 RT우선순위 레벨을 위해 쓰인다) expired 배열은 쓰지 않는다.

sched/rt.c implements SCHED_FIFO and SCHED_RR semantics, in a simpler way than the previous vanilla scheduler did. It uses 100 runqueues (for all 100 RT priority levels, instead of 140 in the previous scheduler) and it needs no expired array.


스케줄링 클래스는 sched_class 구조체를 통해 구현되었다. 이 구조체는 관련된 이벤트가 발생했을 때 반드시 호출되어야 하는 함수에 대한 후킹 함수를 포함하고 있다.

Scheduling classes are implemented through the sched_class structure, which contains hooks to functions that must be called whenever an interesting event occurs.


아래는 후킹 함수의 일부이다.

This is the (partial) list of the hooks:


- enqueue_task(...)

태스크가 runnable 상태가 될 때 이 함수는 태스크의 스케쥴링 엔티티를 레드블랙트리에 넣기 위해 호출된다. 그리고 nr_running 변수를 증가시킨다.

Called when a task enters a runnable state. It puts the scheduling entity (task) into the red-black tree and increments the nr_running variable.


- dequeue_task(...)

태스크가 runnable 상태가 아닐때 이 함수는 해당 태스크를 레드블랙트리에서 빼기 위해 호출된다. 그리고 nr_running 변수를 감소시킨다.

When a task is no longer runnable, this function is called to keep the corresponding scheduling entity out of the red-black tree. It decrements the nr_running variable.


- yield_task(...)

이 함수는 compat_yield sysctl이 켜져있지 않다면 태스크를 레드블랙 트리에서 dequeue 한 다음 다시 enqueue한다. 이 경우에는 태스크를 레드블랙 트리의 가장 오른쪽으로 이동하게 된다.

This function is basically just a dequeue followed by an enqueue,unless the compat_yield sysctl is turned on; in that case, it places the scheduling entity at the right-most end of the red-black tree.


- check_preempt_curr(...)

이 함수는 runnable 상태에 진입한 태스크가 현재 동작중인 태스크를 선점해야 하는지를 체크한다.

This function checks if a task that entered the runnable state should preempt the currently running task.


- pick_next_task(...)

이 함수는 다음에 실행될 가장 적합한 태스크를 선택한다.

This function chooses the most appropriate task eligible to run next.


- set_curr_task(...)

이 함수는 태스크가 자신의 스케쥴링클래스를 변경하거나 자신이 속한 태스크 그룹을 변경할 때 호출된다.

This function is called when a task changes its scheduling class or changes its task group.


- task_tick(...)

이 함수는 대부분 타이머틱 함수에서 호출된다. 함수가 호출되면 실행하는 프로세스가 교체될 수도 있다. 이 함수는 선점을 수행한다.

This function is mostly called from time tick functions; it might lead to process switch. This drives the running preemption.



7. CFS에서의 그룹스케쥴링 확장(GROUP SCHEDULER EXTENSIONS TO CFS)

보통 스케줄러는 개별 태스크 단위와 동작한다. 그리고 각각의 태스크에게 공정하게 CPU 시간을 제공하기 위해 노력한다. 때때로 태스크들을 그룹으로 묶고 CPU 시간을 각 그룹에게  제공하는게 바람직 할 때도 있다. 예를 들어 CPU 시간을 시스템의 각 유저에게 공정하게 나눠주고 각각의 태스크는 유저에 속하는게 바람직할 수 있다.

Normally, the scheduler operates on individual tasks and strives to provide fair CPU time to each task. Sometimes, it may be desirable to group tasks and provide fair CPU time to each such task group. For example, it may be desirable to first provide fair CPU time to each user on the system and then to each task belonging to a user.


CONFIG_CGROUP_SCHED는 위 내용을 정확히 달성하기 위해 노력한다. 이 설정은 태스크들을 그룹으로 만들고 CPU 시간을 각 그룹간에 공평하게 나눠준다.

CONFIG_CGROUP_SCHED strives to achieve exactly that. It lets tasks to be grouped and divides CPU time fairly among such groups.


CONFIG_RT_GROUP_SCHED는 RT 태스크(SCHED_FIFO, SCHED_RR)그룹을 만들 수 있게 한다.

CONFIG_RT_GROUP_SCHED permits to group real-time (i.e., SCHED_FIFO and  SCHED_RR) tasks.


CONFIG_FAIR_GROUP_SCHED는 CFS 태스크(SCHED_NORMAL, SCHED_BATCH)그룹을 만들 수 있게 한다.

CONFIG_FAIR_GROUP_SCHED permits to group CFS 

(i.e., SCHED_NORMAL and SCHED_BATCH) tasks.

이런 커널옵션은 CONFIG_CGROUPS가 정의되어 있어야 사용할 수 있다. 관리자가 cgroup pseudo filesystem을 사용해서 태스크 그룹을 만들게 한다. 이 파일시스템에 대한 추가정보를 원한다면 Documentation/cgroups/cgroups.txt를 보자.

These options need CONFIG_CGROUPS to be defined, and let the administrator create arbitrary groups of tasks, using the "cgroup" pseudo filesystem. See Documentation/cgroups/cgroups.txt for more information about this filesystem.


CONFIG_FAIR_GROUP_SCHED가 정의되어 있다면, pseudo filesystem을 사용해서 만든 각 그룹에 cpu.shares 파일이 생성된다. 태스크그룹을 만들거나 CPU share를 변경하려면 아래 예제를 보자.

When CONFIG_FAIR_GROUP_SCHED is defined, a "cpu.shares" file is created for each group created using the pseudo filesystem. See example steps below to create task groups and modify their CPU share using the "cgroups" pseudo filesystem.


# mount -t tmpfs cgroup_root /sys/fs/cgroup

# mkdir /sys/fs/cgroup/cpu

# mount -t cgroup -ocpu none /sys/fs/cgroup/cpu

# cd /sys/fs/cgroup/cpu

multimedia, browser 태스크 그룹을 생성한다


# mkdir multimedia

# mkdir browser

multimedia 태스크 그룹이 browser 태스크 그룹보다 두배의 CPU 대역폭을 받도록 설정한다.


# echo 2048 > multimedia/cpu.shares

# echo 1024 > browser/cpu.shares

firefox를 실행한 뒤 browser 태스크 그룹에 넣는다.


# firefox &

# echo <firefox_pid> > browser/tasks

movie player를 실행한 뒤 multimedia 태스크 그룹에 넣는다.


# echo <movie_player_pid> > multimedia/tasks


+ Recent posts