-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ProcsBuilder::wait_lock
's invariant
#456
Comments
P가 내 유일한 자녀라고 할 때, 다음 상황이 가능: 현재 구현은 만약 옛날 xv6 구현은 xv6 구현이 현재와 같이 바뀐 이유가
는 아직 확실히 모르겠음. |
@Medowhill
pp->parent = initproc;
// we should wake up init here, but that would require
// initproc->lock, which would be a deadlock, since we hold
// the lock on one of init's children (pp). this is why
// exit() always wakes init (before acquiring any locks).
// we might re-parent a child to init. we can't be precise about
// waking up init, since we can't acquire its lock once we've
// acquired any other proc lock. so wake up init whether that's
// necessary or not. init may miss this wakeup, but that seems
// harmless. |
현재는 코드 형태로 정리하자면 다음과 같습니다. 현재 구조: struct Procs {
process_pool: List<Proc>,
wait_lock: Lock
...
}
struct Proc {
parent: LockProtected<*const Proc>,
data: ProcData,
...
} 논리적인 구조: struct Procs {
process_pool: List<Proc>,
parents: Lock<List<*const Proc>>,
}
struct Proc {
data: ProcData
...
} 실제로 구현을 후자와 같이 수정하는 것을 시도해 볼 수도 있을 것 같습니다. 다만, |
이러면,
등 위와 같은 변화가 필요한 것인지 문의드립니다. |
네. 그럴 것 같습니다. |
"각 |
Linux에서 어떻게 하는지 조사해 보자. |
Linux 코드가 길고 복잡하다보니 제가 제대로 이해했다는 확신은 없습니다만, 지금까지 이해한 내용을 바탕으로 적은 Linux에서 wait의 lost wakeup을 해결하는 방법에 대한 글입니다. Linux의 wait syscall 처리 루틴은 long kernel_wait4(pid_t upid, int __user *stat_addr, int options,
struct rusage *ru)
ret = do_wait(&wo); https://github.com/torvalds/linux/blob/d19cc4bfbff1ae72c3505a00fb8ce0d3fa519e6c/kernel/exit.c#L1630 실질적인 wait 처리는 static long do_wait(struct wait_opts *wo) https://github.com/torvalds/linux/blob/d19cc4bfbff1ae72c3505a00fb8ce0d3fa519e6c/kernel/exit.c#L1442
read_lock(&tasklist_lock);
tsk = current;
do {
retval = do_wait_thread(wo, tsk);
if (retval)
goto end;
static int do_wait_thread(struct wait_opts *wo, struct task_struct *tsk)
{
struct task_struct *p;
list_for_each_entry(p, &tsk->children, sibling) {
int ret = wait_consider_task(wo, 0, p);
if (ret)
return ret;
}
return 0;
} 조건에 부합하는 자녀를 찾았으면 schedule();
goto repeat;
}
}
end:
__set_current_state(TASK_RUNNING);
remove_wait_queue(¤t->signal->wait_chldexit, &wo->child_wait);
return retval; rv6에서는 조건을 만족하는 자녀를 찾기 위해 전체 process의 리스트를 탐색하지만, Linux에서는 각 task마다 자신의 자녀 task의 리스트를 가지고 있어 해당 리스트만 탐색한다는 차이가 있습니다. 이를 제외하면 기본적인 로직은 유사한 것 같습니다. 같은 파일에 exit syscall을 처리하는 루틴도 있습니다. 그중 static void exit_notify(struct task_struct *tsk, int group_dead)
{
bool autoreap;
struct task_struct *p, *n;
LIST_HEAD(dead);
write_lock_irq(&tasklist_lock);
forget_original_parent(tsk, &dead);
if (group_dead)
kill_orphaned_pgrp(tsk->group_leader, NULL);
tsk->exit_state = EXIT_ZOMBIE;
if (unlikely(tsk->ptrace)) {
int sig = thread_group_leader(tsk) &&
thread_group_empty(tsk) &&
!ptrace_reparented(tsk) ?
tsk->exit_signal : SIGCHLD;
autoreap = do_notify_parent(tsk, sig);
} else if (thread_group_leader(tsk)) {
autoreap = thread_group_empty(tsk) &&
do_notify_parent(tsk, tsk->exit_signal); 프로세스 종료 로직 역시 xv6와 Linux에서 큰 차이는 없는 것 같습니다. xv6에서 |
잘 조사해주셔서 감사합니다.
혹시 children iteration이 필요한 다른 syscall이 있나요? children list를 따로 관리할 motivation이 궁금해서 질문드립니다. 우리도 Linux 디자인을 따라가는게 어떨지 제안드립니다. 다만 제가 이해한게 맞다면 |
아니면 심지어 한술 더떠서... completely lock-free kernel을 상상해볼 수도 있겠습니다. Rust를 이용해서 적은 비용으로 리팩토링 잘 해서 lock-free design을 만들었다는게 key contribution... 아마 다음 future work에서... |
grep으로
/*
* A task is exiting. If it owned this mm, find a new owner for the mm.
*/
void mm_update_next_owner(struct mm_struct *mm)
{
...
/*
* Search in the children
*/
list_for_each_entry(c, &p->children, sibling) {
if (c->mm == mm)
goto assign_new_owner;
}
/*
* Search in the siblings
*/
list_for_each_entry(c, &p->real_parent->children, sibling) {
if (c->mm == mm)
goto assign_new_owner;
}
/*
* The sum of our children's runtime should not exceed our own.
*/
list_for_each_entry_rcu(child, &tg->children, siblings) {
period = ktime_to_ns(child->rt_bandwidth.rt_period);
runtime = child->rt_bandwidth.rt_runtime;
if (child == d->tg) {
period = d->rt_period;
runtime = d->rt_runtime;
}
sum += to_ratio(period, runtime);
}
if (sum > total)
return -EINVAL; Linux 디자인을 따라간다는 것이 어떤 뜻인가요? Linux처럼 자녀 프로세스 리스트를 만들어서 관리하자는 말씀이신가요? 아니면 지금처럼 wait_lock을 사용하는 설계를 유지하자는 말씀이신가요? Linux에는 parent proc을 보호하는 lock은 따로 없는 것 같습니다. xv6에서 parent proc을 보호하는 락이 별도로 존재하는 이유는 wait에서 lost wakeup이 생기는 것을 막기 위함인데, Linux에서는 어차피 |
이제 |
ProcsBuilder::init
은self.wait_lock
을&'static
으로 빌려서 다른 객체에 저장합니다. 따라서self
가&'static
으로 빌려진 상태이므로&mut
으로 다시 빌리면 원래는 안 됩니다. 하지만, 실제로는self.wait_lock
에는 앞으로 다시 접근하지 않을 것이기 때문에&mut
으로 빌려도 문제가 발생하지 않습니다. 그래서Procs
의 invariant로wait_lock
에 접근하지 않는다는 내용을 추가하고,self
를&mut ProcsBuilder
대신&mut Procs
타입으로 만들어 리턴합니다. 다만self
가 이미&'static
으로 빌려진 상태에서&mut Procs
를 만들려고 하면 ownership 검사를 통과할 수 없기 때문에,self.wait_lock
을 빌릴 때 raw pointer로 변환하는 과정을 거쳐self
가&'static
으로 빌려진 상태라는 것을 컴파일러가 모르게 만들었습니다.Originally posted by @Medowhill in #454 (comment)
ProcsBuilder::wait_lock
's invariant에 대한 논의와 정리가 필요The text was updated successfully, but these errors were encountered: