티스토리 뷰
8 인터럽트
8.4 인터럽트 지연처리
8.4.1 top-half, bottom-half
- 인터럽트 핸들러에서 인터럽트를 처리하는 동인 인터럽트는 비활성화 상태가 됨.
- 이때 blocking 되어 데드락이 걸리거나 인터럽트를 놓칠 수 있고, sleep 하는 경우는 깨어나지 못할 수도 있음
- 이런 문제를 방지하기 위해, 인터럽트 핸들러를 별도의 안전한 컨텍스트에서 실행함
구분 | top-half | bottom-half |
---|---|---|
실행 위치 | 하드웨어 인터럽트 핸들러 내에서 수행 | 하드웨어 인터럽트 핸들러 밖에서 수행 |
실행 컨텍스트 | 하드웨어 인터럽트 컨텍스트 | 소프트웨어 인터럽트 컨텍스트 or 프로세서 컨텍스트 |
인터럽트 활성화 여부 | 인터럽트 비활성화 상태 | 인터럽트 활성화 상태 |
주요 작업 | 다음 인터럽트 발생 가능하도록 최소한의 작업 수행 | 처리시간이 길거나 블록 될 수 있는 지연된 작업 처리 |
top-half: ISR(Interrupt Service Routine) 에서 bottom-half 에 분배
hard IRQ 와 soft IRQ
아키텍처 관점
- HW IRQ: 하드웨어 장치가 발생하는 인터럽트
- SW IRQ: 소프트웨어(시스템 콜, 예외)가 발생시키는 인터럽트
리눅스 커널 관점
- Hard IRQ: 인터럽트가 발생하면 즉시 실행됨 (짧고 빠르게 처리)
- Soft IRQ: Hard IRQ에서 넘겨받은 작업을 나중에 실행 (네트워크 패킷, 블록 디바이스 I/O 처리)
Hard IRQ와 Soft IRQ는 인터럽트 처리에 관한 개념이고, Top-half와 Bottom-half는 인터럽트 처리의 흐름을 설명하는 개념
soft IRQ 와 SW IRQ 는 전혀 다른 개념. softIRQ 는 인터럽트를 지연 처리 하는것
$ cat /proc/softirqs
CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7 CPU8 CPU9 CPU10 CPU11
HI: 0 0 0 0 0 0 0 0 0 0 0 0
TIMER: 13662 11818 18707 16318 20230 14917 14078 8432 16445 6710 14198 6197
NET_TX: 0 0 0 0 0 0 0 0 0 0 0 0
NET_RX: 1854 1417 1742 704 3880 1002 1880 1160 1877 820 1949 1136
BLOCK: 0 0 0 0 0 0 0 0 0 0 0 0
IRQ_POLL: 0 0 0 0 0 0 0 0 0 0 0 0
TASKLET: 129745 109616 100553 0 1 0 0 0 0 0 0 0
SCHED: 68081 41242 51434 93770 63607 32627 55538 22766 57432 18298 52017 22680
HRTIMER: 0 0 0 0 0 1 0 0 0 0 0 0
RCU: 53004 41567 59739 85283 63843 40492 58084 31793 58515 27775 56497 32467
8-11 kernel/softirq.c softirq_init
void __init softirq_init(void)
{
int cpu;
for_each_possible_cpu(cpu) {
per_cpu(tasklet_vec, cpu).tail =
&per_cpu(tasklet_vec, cpu).head; // tail 을 head 에 연결만하네
per_cpu(tasklet_hi_vec, cpu).tail =
&per_cpu(tasklet_hi_vec, cpu).head;
}
open_softirq(TASKLET_SOFTIRQ, tasklet_action); // TASKLET_SOFTIRQ
open_softirq(HI_SOFTIRQ, tasklet_hi_action); // HI_SOFTIRQ
}
SOFTIRQ 와 TASKLET 비교
tasklet 은 softirq 개념 위에서 동작하며, HI_SOFTIRQ 와 TASKLET_SOFTIRQ 에서 동작함
tasklet은 런타임에 할당 및 초기화 될 수있는 softirqs
softirq와 달리 동일한 유형의 tasklet은 한 번에 여러 프로세서에서 실행될 수 없음.
특징 | softirq | tasklet |
---|---|---|
동시성 | 같은 프로레서에서 실행 가능 | 같은 tasklet 은 다른 cpu 에서 실행 될 수 없음 |
등록 방법 | 컴파일시 정적 등록 | 런타임 시 동적 등록 |
우선순위 | softirq 마다 우선순위 | HI, TASKLET 으로 구분 |
사용하는곳 | 빈번함. 병렬처리 필요한 곳 | 직렬 처리가 필요한곳. 디바이스 드라이버에서 필요한 경우 사용 가능 |
kernel/softirq.c raise_softirq
/*
* This function must run with irqs disabled!
*/
inline void raise_softirq_irqoff(unsigned int nr)
{
__raise_softirq_irqoff(nr);
// #define in_interrupt() (irq_count())
// #define irq_count() (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK))
if (!in_interrupt())
wakeup_softirqd();
}
void raise_softirq(unsigned int nr)
{
unsigned long flags;
local_irq_save(flags);
raise_softirq_irqoff(nr);
local_irq_restore(flags);
}
void __raise_softirq_irqoff(unsigned int nr)
{
trace_softirq_raise(nr);
or_softirq_pending(1UL << nr);
}
static void wakeup_softirqd(void)
{
/* Interrupts are disabled: no need to stop preemption */
struct task_struct *tsk = __this_cpu_read(ksoftirqd);
if (tsk && tsk->state != TASK_RUNNING)
wake_up_process(tsk);
}
8-13 kernel/softirq.c __do_soft_irq
asmlinkage __visible void __softirq_entry __do_softirq(void)
{
// jiffies 시스템에 내장된 타이머에서 인터럽트 된 마지막 시간
unsigned long end = jiffies + MAX_SOFTIRQ_TIME; // 최대 수행 시간
unsigned long old_flags = current->flags;
int max_restart = MAX_SOFTIRQ_RESTART; // 최대 retry 횟수
pending = local_softirq_pending(); // 마스킹 된 softirq 있는지 확인
account_irq_enter_time(current);
// IP : Instruction Pointer
__local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET); 선점 못하게
in_hardirq = lockdep_softirq_start();
restart:
set_softirq_pending(0);
local_irq_enable();
h = softirq_vec;
while ((softirq_bit = ffs(pending))) { // 우선순위 높은거부터
unsigned int vec_nr;
int prev_count;
h += softirq_bit - 1;
vec_nr = h - softirq_vec;
prev_count = preempt_count();
h->action(h); // 실행
h++;
pending >>= softirq_bit;
}
rcu_bh_qs();
local_irq_disable();
pending = local_softirq_pending();
if (pending) {
if (time_before(jiffies, end) && !need_resched() && // 한번 더
--max_restart)
goto restart;
wakeup_softirqd();
}
}
8.4.3 워크큐
프로세스 컨특스트에서 실해오디어, 긴 시간동안 처리해야 하는 작업에 적합하다.
softirq 는 인터럽트 발생 외 컨텍스트 스위칭이 발생하지 않지만, 워크 큐는 유저 태스크를 포함하여 다른 태스크가 선점이 가능하다.
워크큐의 개념
워커는 작업이 없으면 IDLE 상태로 진입했다가, 작업이 도착하면 깨어나 동작한다. 워커 쓰레드는 워커 풀에 의해 관리된다.
cmwq (Concurrency-Managed Workqueues)
워커 관리와 동시성 향상을 위해 추가된 워크 큐
기존 방식
- Multi Thread (MT) 워커 : cpu 당 하나의 워커. cpu 개수만큼 사용하여 불필요하게 많은 워커쓰레드가 필요.
- Single Thread (ST) 워커 : 시스템 당 하나
구분 | bound | unbound |
---|---|---|
동작 방식 | 특정 cpu 에서 동작 | 동작할 cpu 를 지정하지 않음 (작업 요청 cpu 우선) |
pwq: pool workqueue. 워크 큐와 워크 풀은 풀 워크큐를 통해 연결됨. 동시처리가 지연되어 워커 풀에 들어가지 못하는 경우, 풀 워크큐에 등록
cpu 별로 보통 우선순위, 높은 우선순위 풀을 두개씩 갖고 있음.
__queue_work
work_struct 로 워크큐에 등록할 작업 구조체를 선언하고 초기화.
- schedule_XXX : system_wq 워크큐에 등록
- XXX_on : bound 로 cpu 를 지정
- XXX_delayed_XXX : 지연 시간 이후 동작하도록 등록
#define DECLARE_WORK(n, f) \
struct work_struct n = __WORK_INITIALIZER(n, f)
#define INIT_WORK(_work, _func) \
__INIT_WORK((_work), (_func), 0)
8-14 kernel/workqueue.c __queue_work
struct workqueue_struct { // 작업 큐 전체를 대표하는 구조체
struct list_head pwqs; /* WR: all pwqs of this wq */
struct list_head list; /* PR: list of all workqueues */
...
}
struct pool_workqueue { //
struct worker_pool *pool; /* I: the associated pool */
struct workqueue_struct *wq; /* I: the owning workqueue */
int work_color; /* L: current color */
int flush_color; /* L: flushing color */
int refcnt; /* L: reference count */
int nr_in_flight[WORK_NR_COLORS];
...
}
struct worker_pool { // 워커 쓰레드 그룹
spinlock_t lock; /* the pool lock */
int cpu; /* I: the associated cpu */
int node; /* I: the associated node ID */
int id; /* I: pool ID */
unsigned int flags; /* X: flags */
unsigned long watchdog_ts; /* L: watchdog timestamp */
struct list_head worklist; /* L: list of pending works */
int nr_workers; /* L: total number of workers */
...
}
workqueue_struct 는 종류별로 필요한 만큼 만들 수 있음
extern struct workqueue_struct *system_wq;
extern struct workqueue_struct *system_highpri_wq;
extern struct workqueue_struct *system_long_wq;
extern struct workqueue_struct *system_unbound_wq;
extern struct workqueue_struct *system_freezable_wq;
extern struct workqueue_struct *system_power_efficient_wq;
extern struct workqueue_struct *system_freezable_power_efficient_wq;
extern struct workqueue_struct *system_bh_wq;
extern struct workqueue_struct *system_bh_highpri_wq;
pwq (pool_workqueue) 는 기본적으로 CPU 바운드라면 CPU 개수만큼, UNBOUND라면 1개만 생성됨.
worker_pool은 task(작업)를 실행할 수 있는 워커(worker) 스레드 그룹을 관리하는 구조체
static void __queue_work(int cpu, struct workqueue_struct *wq,
struct work_struct *work)
{
if (unlikely(wq->flags & __WQ_DRAINING) && // 실행중인 쓰레드가 내 work 의 쓰레드가 동일할때만 queue 하는 DRAINING flag
WARN_ON_ONCE(!is_chained_work(wq)))
return;
retry:
if (req_cpu == WORK_CPU_UNBOUND) // 지정된 cpu 부터 시작하여 round robing 으로 cpu 를 지정
cpu = wq_select_unbound_cpu(raw_smp_processor_id());
if (!(wq->flags & WQ_UNBOUND)) // bound 인경우
pwq = per_cpu_ptr(wq->cpu_pwqs, cpu); // cpu마다 pool_workqueue 구조체를 가짐. cpu 에 맞는 pwq 를 가져옴.
else
pwq = unbound_pwq_by_node(wq, cpu_to_node(cpu)); // cpu 가 속한 node 에서 unbound 용 pwq 를 가져옴
last_pool = get_work_pool(work); // 마지막 사용 풀
if (last_pool && last_pool != pwq->pool) {
struct worker *worker;
spin_lock(&last_pool->lock);
worker = find_worker_executing_work(last_pool, work); // 풀 안에있는 worker 중 실행가능한? 워커를 가져옴
if (worker && worker->current_pwq->wq == wq) { // 워크큐와 같다면
pwq = worker->current_pwq // worker 의 pwq 를 사용
} else {
/* meh... not running there, queue here */
spin_unlock(&last_pool->lock);
spin_lock(&pwq->pool->lock); // 새로 사용할 pool 에다가 Lock 을 잡음
}
} else { // pwq 와 work 의 pool 이 같은 경우
spin_lock(&pwq->pool->lock); // 해당 pool 을 사용
}
if (unlikely(!pwq->refcnt)) {
if (wq->flags & WQ_UNBOUND) { // 버그상황인 경우, unbound 에서는 다른 cpu 를 찾아서 실행하도록 retry
spin_unlock(&pwq->pool->lock);
cpu_relax();
goto retry;
}
/* oops */
WARN_ONCE(true, "workqueue: per-cpu pwq for %s on cpu%d has 0 refcnt",
wq->name, cpu); // bound 로 cpu 가 지정된 경우는 어쩔수 없으니 경고를 띄움
}
/* pwq determined, queue */
trace_workqueue_queue_work(req_cpu, pwq, work);
if (WARN_ON(!list_empty(&work->entry))) {
spin_unlock(&pwq->pool->lock);
return;
}
// static int work_next_color(int color)
// {
// return (color + 1) % WORK_NR_COLORS;
// }
pwq->nr_in_flight[pwq->work_color]++; // 현재 실행중인 color 에 + 1
work_flags = work_color_to_flags(pwq->work_color);
if (likely(pwq->nr_active < pwq->max_active)) {
trace_workqueue_activate_work(work);
pwq->nr_active++;
worklist = &pwq->pool->worklist; // 실행 가능한 상태인 경우 pool->worklist 로 지정
if (list_empty(worklist))
pwq->pool->watchdog_ts = jiffies;
} else {
work_flags |= WORK_STRUCT_DELAYED;
worklist = &pwq->delayed_works; // 실행 불가능한 상태인 경우 pwq->delayed_works 로 지정
}
insert_work(pwq, work, worklist, work_flags); // work 를 list 에 삽입
spin_unlock(&pwq->pool->lock);
}
kernel/workqueue.c worker_thread
enum {
WORK_STRUCT_PENDING_BIT = 0, /* work item is pending execution */
WORK_STRUCT_DELAYED_BIT = 1, /* work item is delayed */
WORK_STRUCT_PWQ_BIT = 2, /* data points to pwq */
WORK_STRUCT_LINKED_BIT = 3, /* next work is linked to this one */
#ifdef CONFIG_DEBUG_OBJECTS_WORK
WORK_STRUCT_STATIC_BIT = 4, /* static initializer (debugobjects) */
WORK_STRUCT_COLOR_SHIFT = 5, /* color for workqueue flushing */
#else
WORK_STRUCT_COLOR_SHIFT = 4, /* color for workqueue flushing */
#endif
WORK_STRUCT_COLOR_BITS = 4,
WORK_STRUCT_PENDING = 1 << WORK_STRUCT_PENDING_BIT,
WORK_STRUCT_DELAYED = 1 << WORK_STRUCT_DELAYED_BIT,
WORK_STRUCT_PWQ = 1 << WORK_STRUCT_PWQ_BIT,
WORK_STRUCT_LINKED = 1 << WORK_STRUCT_LINKED_BIT,
}
// nr_running == 0이면 아무 worker도 현재 일을 하고 있지 않은 상태고,
// nr_running == 1이면 현재 딱 하나의 worker만 일하는 중
static bool keep_working(struct worker_pool *pool)
{
return !list_empty(&pool->worklist) &&
atomic_read(&pool->nr_running) <= 1;
}
// worker pool 에 등록된 작업을 갸져와서 실행하기 위함
// bottom-half 로 무거운 작업들이 work queue 에 담겨있음
static int worker_thread(void *__worker)
{
struct worker *worker = __worker;
struct worker_pool *pool = worker->pool;
worker->task->flags |= PF_WQ_WORKER; // 현재 task 가 worker thread 임을 등록. 이후 counter 에 추가
woke_up:
spin_lock_irq(&pool->lock);
/* am I supposed to die? */
if (unlikely(worker->flags & WORKER_DIE)) {
... // worker 가 죽은 경우 처리
return 0;
}
worker_leave_idle(worker); // 워커가 일을 할 예정으로idled 리스트에서 빼옴
recheck:
// return !list_empty(&pool->worklist) && !atomic_read(&pool->nr_running);
if (!need_more_worker(pool)) // 다른 워커를 깨워야 할 필요가 없다면, queue 에 아이템이 없고 워커들이 돌지 않는 경우
goto sleep;
/* do we need to manage? */
// may_start_working { return pool->nr_idle; }
// manage_workers { manage_arb 락 잡은 경우 }. arb = arbitration = 조정중
if (unlikely(!may_start_working(pool)) && manage_workers(worker))
goto recheck;
WARN_ON_ONCE(!list_empty(&worker->scheduled)); // worker 가 준비 되기전에 scheduled 된게 있었으면 경고
worker_clr_flags(worker, WORKER_PREP | WORKER_REBOUND); // PREP, REBOUND 플래그를 제거
do {
struct work_struct *work = // 풀의 첫번째 작업 가져오기
list_first_entry(&pool->worklist,
struct work_struct, entry);
pool->watchdog_ts = jiffies; // 풀의 watchdog(주어진 시간에 특정 일이 발생하는지 감시하는 기능) 에 현재시간 등록
if (likely(!(*work_data_bits(work) & WORK_STRUCT_LINKED))) { // 작업이 링크되어있지 않다면. 다음 work 가 연결되어있지 않은 경우
/* optimization path, not strictly necessary */
process_one_work(worker, work); // 워커에게 하나의 일을 수행 시킴
if (unlikely(!list_empty(&worker->scheduled))) // 워커에 스케줄된 작업이 있다면 수행시킴
process_scheduled_works(worker);
} else {
move_linked_works(work, &worker->scheduled, NULL); // link 되어있었다면 스케줄 리스트에 연결
process_scheduled_works(worker); // 스케줄된 작업 수행
}
} while (keep_working(pool)); // 워커 풀이 다 처리 될때까지 반복
worker_set_flags(worker, WORKER_PREP); // sleep 시키기 위해 준비중 상태로 변경
sleep:
worker_enter_idle(worker); // idle 상태로 변경
__set_current_state(TASK_INTERRUPTIBLE);
spin_unlock_irq(&pool->lock);
schedule();
goto woke_up;
}
- process 시점에는 lock 을 풀고 처리함. 그래서 병렬적으로 돔
static void process_one_work(struct worker *worker, struct work_struct *work) __releases(&pool->lock) __acquires(&pool->lock) {
kernel/workqueue.c init_workqueues
8.4.3 에서 나온 unbound, pwq
구분 | bound | unbound |
---|---|---|
동작 방식 | 특정 cpu 에서 동작 | 동작할 cpu 를 지정하지 않음 (작업 요청 cpu 우선) |
pwq(per-cpu workqueue): pool workqueue. 워크 큐와 워크 풀은 풀 워크큐를 통해 연결됨. 동시처리가 지연되어 워커 풀에 들어가지 못하는 경우, 풀 워크큐에 등록
workqueue(bound) [pwq0, pwq1, pwq2, pwq3] / workqueue(unbound) [pwq]
workqueue(bound) [pwq0, pwq1, pwq2, pwq3] / workqueue(unbound) [pwq]
.. (여러개 존재)
cpu0 [pool-normal, pool-prio] (각 pool 별로 worker 를 가짐)
cpu1 [pool-normal, pool-prio]
cpu2 [pool-normal, pool-prio]
cpu3 [pool-normal, pool-prio]
static int __init init_workqueues(void)
{
int std_nice[NR_STD_WORKER_POOLS] = { 0, HIGHPRI_NICE_LEVEL }; // 워커풀을 normal, prio 로 관리
int i, cpu;
WARN_ON(__alignof__(struct pool_workqueue) < __alignof__(long long));
BUG_ON(!alloc_cpumask_var(&wq_unbound_cpumask, GFP_KERNEL));
// possible mask 값을 unbound_cpumask 로 복사해옴
// 모든 cpu 가 unbound task 를 사용 가능한 상태로
cpumask_copy(wq_unbound_cpumask, cpu_possible_mask);
// bound 는 per-cpu 로 관리되기 때문에
// unbound 만 slab 캐시로 생성
pwq_cache = KMEM_CACHE(pool_workqueue, SLAB_PANIC);
cpu_notifier(workqueue_cpu_up_callback, CPU_PRI_WORKQUEUE_UP); // 이벤트 콜백 등록
hotcpu_notifier(workqueue_cpu_down_callback, CPU_PRI_WORKQUEUE_DOWN);
wq_numa_init();
/* initialize CPU pools */
for_each_possible_cpu(cpu) { // possible cpu 에 대한 워커풀을 순회
struct worker_pool *pool;
i = 0;
for_each_cpu_worker_pool(pool, cpu) { // pool 은 per-cpu 로 관리됨. normal, prio 두개
BUG_ON(init_worker_pool(pool));
pool->cpu = cpu;
cpumask_copy(pool->attrs->cpumask, cpumask_of(cpu));
pool->attrs->nice = std_nice[i++];
pool->node = cpu_to_node(cpu);
/* alloc pool ID */
mutex_lock(&wq_pool_mutex);
BUG_ON(worker_pool_assign_id(pool));
mutex_unlock(&wq_pool_mutex);
}
}
/* create the initial worker */
for_each_online_cpu(cpu) {
struct worker_pool *pool;
for_each_cpu_worker_pool(pool, cpu) {
pool->flags &= ~POOL_DISASSOCIATED;
BUG_ON(!create_worker(pool)); pool 에 초기 워커 생성
}
}
kernel/workqueue.c init_workqueues 2
/* create default unbound and ordered wq attrs */
for (i = 0; i < NR_STD_WORKER_POOLS; i++) {
struct workqueue_attrs *attrs;
BUG_ON(!(attrs = alloc_workqueue_attrs(GFP_KERNEL)));
attrs->nice = std_nice[i];
unbound_std_wq_attrs[i] = attrs; // unbound attr 설정
/*
* An ordered wq should have only one pwq as ordering is
* guaranteed by max_active which is enforced by pwqs.
* Turn off NUMA so that dfl_pwq is used for all nodes.
*/
BUG_ON(!(attrs = alloc_workqueue_attrs(GFP_KERNEL)));
attrs->nice = std_nice[i];
attrs->no_numa = true;
ordered_wq_attrs[i] = attrs; // bound attr 설정
}
// 여기서 미리 지정한 이유는 재사용하기 위함임. unboud, order
// static int alloc_and_link_pwqs(struct workqueue_struct *wq)
// 각 WORK QUEUE 들 생성
// allock_work queue 에서 위에 정해둔 attr 을 따름
system_wq = alloc_workqueue("events", 0, 0);
system_highpri_wq = alloc_workqueue("events_highpri", WQ_HIGHPRI, 0);
system_long_wq = alloc_workqueue("events_long", 0, 0);
system_unbound_wq = alloc_workqueue("events_unbound", WQ_UNBOUND,
WQ_UNBOUND_MAX_ACTIVE);
system_freezable_wq = alloc_workqueue("events_freezable",
WQ_FREEZABLE, 0);
system_power_efficient_wq = alloc_workqueue("events_power_efficient",
WQ_POWER_EFFICIENT, 0);
system_freezable_power_efficient_wq = alloc_workqueue("events_freezable_power_efficient",
WQ_FREEZABLE | WQ_POWER_EFFICIENT,
0);
BUG_ON(!system_wq || !system_highpri_wq || !system_long_wq ||
!system_unbound_wq || !system_freezable_wq ||
!system_power_efficient_wq ||
!system_freezable_power_efficient_wq);
wq_watchdog_init();
return 0;
}
early_initcall(init_workqueues);
enum {
WQ_UNBOUND = 1 << 1, /* not bound to any cpu */
WQ_FREEZABLE = 1 << 2, /* freeze during suspend */
WQ_MEM_RECLAIM = 1 << 3, /* may be used for memory reclaim */
WQ_HIGHPRI = 1 << 4, /* high priority */
WQ_CPU_INTENSIVE = 1 << 5, /* cpu intensive workqueue */
WQ_SYSFS = 1 << 6, /* visible in sysfs, see wq_sysfs_register() */
}
8.4.4 threaded irq
각각의 인터럽트를 위한 별도의 커널 쓰레드를 만들어 처리하는 개념.
구분 | irq | threaded irq |
---|---|---|
top-half | IRQ context | IRQ context |
bottom-half | tasklet, workqueue | thread_fn (kernel thread) |
좀더 사용자 원하는대로 커스텀 하는것이 thread_irq
kernel/irq/manage.c setup_irq_thread
static int
setup_irq_thread(struct irqaction *new, unsigned int irq, bool secondary)
{
struct task_struct *t;
struct sched_param param = {
.sched_priority = MAX_USER_RT_PRIO/2,
};
if (!secondary) { // primary case
t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
new->name);
} else { // secondary case
t = kthread_create(irq_thread, new, "irq/%d-s-%s", irq,
new->name);
param.sched_priority -= 1;
}
if (IS_ERR(t))
return PTR_ERR(t);
sched_setscheduler_nocheck(t, SCHED_FIFO, ¶m);
get_task_struct(t); // 해제 못하도록 counter + 1
new->thread = t;
set_bit(IRQTF_AFFINITY, &new->thread_flags);
return 0;
}
'개발 > 코드로 알아보는 ARM 리눅스 커널 TIL' 카테고리의 다른 글
250419 9.3 타이머 관리 [완] (0) | 2025.04.19 |
---|---|
250412 공통 클록 프레임워크 & 타임 서브시스템 (9.1~9.2) (0) | 2025.04.12 |
250329 인터럽트 개념과 핸들러 등록 (8.1~8.3) (0) | 2025.03.29 |
250322 Secondary Booting (7.3) (0) | 2025.03.22 |
250317 SMP 를 위한 커널지원 & cpu topology(7.1~7.2) (0) | 2025.03.17 |
- Total
- Today
- Yesterday
- set value
- boost
- C++
- 영상 픽셀화 하기
- red underline
- ad skip
- 우리는 vr핏이라고 부릅니다
- 잘못된 빨간줄
- 봄날에 스케치
- shared_from_this
- chrome-extension
- vr핏
- 클래스 맴버 변수 출력하기
- Golang
- Reciprocal n-body Collision Avoidance
- Visual Studio
- Quest2
- 코어 남기기
- mysql
- RVO
- SuffixArray
- print shared_ptr class member variable
- vrpit
- 에러 위치 찾기
- cockroach db
- hole-punching
- 카카오
- it's called a vrpit
- 면접
- Obstacle Avoidance
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |