I am trying to implement kernel level threads in xv6.
My main problem at the moment is to understand how the CPU gets its information about the current process and how to modify it to point to the current thread instead.
I know it is somehow linked to this line:
extern struct proc *proc asm("%gs:4");
in proc.h, but I do not fully understand how and why it works.
I found out %gs points to to the line struct cpu *cpu; in the struct cpu (defined at proc.h), and right below that line (+ 4 bytes after the cpu pointer)
the current process of the cpu is stored:
struct proc *proc; // The currently-running process.
so in order to add thread support one should either alter this line to point to the new thread struct instead of process struct or alternatively, add the thread below the "proc" line and perform the following changes:
add in proc.h the following decleration: extern struct thread *thread asm("%gs:8");
change in vm.c, in fucntion "seginit(void)" the line
c->gdt[SEG_KCPU] = SEG(STA_W, &c->cpu, 8, 0); to c->gdt[SEG_KCPU] = SEG(STA_W, &c->cpu, 12, 0); in order to allocate space for the extra thread pointer.
Related
I have kernel task that create kernel thread ,and I need to copy data to the user which call my kernel task ,from my kernel thread . So I can pass the current task as parameter to my kernel thread.
But how can I tell the copy_from_user function to copy from other process address space.
this my kernel task
asmlinkage int sys_daniel(struct pt_regs *r )
{
struct task_struct *ts1;
ts1 = kthread_run(kthread_func, current, "thread-1");
return 0;
}
and this the kernel thread I am tring to write
static int kthread_func(struct_task args)
{
spcail_copy_to_user(from,to,len,args->mm)
}
there is any way to edit the kernel thread current->mm or to set in the copy_from_user the address space.
ok so you first need to create a page object with the address you want so I used the
struct page *P
get_user_pages(current,current->mm,(unsigned long)buff,1,1,&p,NULL)
so this basically create page that now we can map to the kernel
so we can use
kernlBuff=(char*)kmap(p);//mapping it to the kernel
kunmap(p);//for unmapp it from the kernel
I'm trying to understand more about process 0, such as, whether it has a memory descriptor (non-NULL task_struct->mm field) or not, and how is it related to the swap or idle process. It seems to me that a single 'process 0' is created on the boot cpu, and then an idle thread is created for every other cpu by idle_threads_init, but I didn't find where the first one( I assume that is the process 0) was created.
Update
In light of the live book that tychen referenced, here is my most up-to-date understanding regarding process 0 (for x86_64), can someone confirm/refute the items below?
An init_task typed task_struct is statically defined, with the task's kernel stack init_task.stack = init_stack, memory descriptor init_task.mm=NULL and init_task.active_mm=&init_mm, where the stack area init_stack and mm_struct init_mm are both statically defined.
The fact that only active_mm is non-NULL means process 0 is a kernel process. Also, init_task.flags=PF_KTHREAD.
Not long after the uncompressed kernel image begins execution, boot cpu starts to use init_stack as kernel stack. This makes the current macro meaningful (for the first time since machine boots up), which makes fork() possible. After this point, the kernel literally runs in process 0's conext.
start_kernel -> arch_call_rest_init -> rest_init, and inside this function, process 1&2 are forked. Within the kernel_init function which is scheduled for process 1, a new thread (with CLONE_VM) is made and hooked to a CPU's run queue's rq->idle, for every other logical CPU.
Interestingly, all idle threads share the same tid 0 (not only tgid). Usually threads share tgid but have distinct tid, which is really Linux's process id. I guess it doesn't break anything because idle threads are locked to their own CPUs.
kernel_init loads the init executable (typically /sbin/init), and switches both current->mm and active_mm to a non-NULL mm_struct, and clears the PF_KTHREAD flag, which makes process 1 a legitimate user space process. While process 2 does not tweak mm, meaning it remains a kernel process, same as process 0.
At the end of rest_init, do_idle takes over, which means all CPU has an idle process.
Something confused me before, but now becomes clear: the init_* objects/labels such as init_task/init_mm/init_stack are all used by process 0, and not the init process, which is process 1.
We really start Linux kernel from start_kernel, and the process 0/idle starts here too.
In the begin of start_kernel, we call set_task_stack_end_magic(&init_stack). This function will set the stack border of init_task, which is the process 0/idle.
void set_task_stack_end_magic(struct task_struct *tsk)
{
unsigned long *stackend;
stackend = end_of_stack(tsk);
*stackend = STACK_END_MAGIC; /* for overflow detection */
}
It's easy to understand that this function get the limitation address and set the bottom to STACK_END_MAGIC as a stack overflow flag. Here is the structure graph.
The process 0 is statically defined . This is the only process that is not created by kernel_thread nor fork.
/*
* Set up the first task table, touch at your own risk!. Base=0,
* limit=0x1fffff (=2MB)
*/
struct task_struct init_task
#ifdef CONFIG_ARCH_TASK_STRUCT_ON_STACK
__init_task_data
#endif
= {
#ifdef CONFIG_THREAD_INFO_IN_TASK
.thread_info = INIT_THREAD_INFO(init_task),
.stack_refcount = REFCOUNT_INIT(1),
#endif
.state = 0,
.stack = init_stack,
.usage = REFCOUNT_INIT(2),
.flags = PF_KTHREAD,
.prio = MAX_PRIO - 20,
.static_prio = MAX_PRIO - 20,
.normal_prio = MAX_PRIO - 20,
.policy = SCHED_NORMAL,
.cpus_ptr = &init_task.cpus_mask,
.cpus_mask = CPU_MASK_ALL,
.nr_cpus_allowed= NR_CPUS,
.mm = NULL,
.active_mm = &init_mm,
......
.thread_pid = &init_struct_pid,
.thread_group = LIST_HEAD_INIT(init_task.thread_group),
.thread_node = LIST_HEAD_INIT(init_signals.thread_head),
......
};
EXPORT_SYMBOL(init_task);
Here are some important thins we need to make it clearly.
INIT_THREAD_INFO(init_task) sets the thread_info as the graph above.
init_stack is defined as below
extern unsigned long init_stack[THREAD_SIZE / sizeof(unsigned long)];
where THREAD_SIZE equal to
#ifdef CONFIG_KASAN
#define KASAN_STACK_ORDER 1
#else
#define KASAN_STACK_ORDER 0
#endif
#define THREAD_SIZE_ORDER (2 + KASAN_STACK_ORDER)
#define THREAD_SIZE (PAGE_SIZE << THREAD_SIZE_ORDER)
so the default size is defined.
The process 0 will only run in kernel space, but in some circumstances as I mention above it needs a virtual memory space, so we set the following
.mm = NULL,
.active_mm = &init_mm,
Let's look back at start_kernel, the rest_init will initialize kernel_init and kthreadd.
noinline void __ref rest_init(void)
{
......
pid = kernel_thread(kernel_init, NULL, CLONE_FS);
......
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
......
}
kernel_init will run execve and then go to user space, change to init process by running , which is process 1.
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
return 0;
kthread becomes the daemon process to manage and schedule other kernel task_struts, which is process 2.
After all this, the process 0 will become idle process and jump out rq which means it will only run when the rq is empty.
noinline void __ref rest_init(void)
{
......
/*
* The boot idle thread must execute schedule()
* at least once to get things moving:
*/
schedule_preempt_disabled();
/* Call into cpu_idle with preempt disabled */
cpu_startup_entry(CPUHP_ONLINE);
}
void cpu_startup_entry(enum cpuhp_state state)
{
arch_cpu_idle_prepare();
cpuhp_online_idle(state);
while (1)
do_idle();
}
Finally, here is a good gitbook for you if you want to get more understanding of Linux kernel.
I'm talking about:
struct {
struct spinlock lock;
struct proc proc[NPROC];
} ptable;
which resides in proc.c file.
Can someone please explain where it is initialized?
Because, in proc.c I've never seen something (process) being added to it.
To be more precise, let's say I'm looking at the scheduler code:
void
scheduler(void)
{
struct proc *p;
for(;;){
// Enable interrupts on this processor.
sti();
// Loop over process table looking for process to run.
acquire(&ptable.lock);
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
if(p−>state != RUNNABLE)
continue;
// Switch to chosen process. It is the process’s job
// to release ptable.lock and then reacquire it
// before jumping back to us.
proc = p;
switchuvm(p);
p−>state = RUNNING;
swtch(&cpu−>scheduler, proc−>context);
switchkvm();
// Process is done running for now.
// It should have changed its p−>state before coming back.
proc = 0;
}
release(&ptable.lock);
}
}
In:
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
you can see that we are looping thorough each process in ptable. My question is, how did they get there?
Thanks!
It is initialised.
struct {
struct spinlock lock;
struct proc proc[NPROC];
} ptable;
the above code defines a struct (with no name) and initialises ptable as one struct of this type.
maybe you are confusing with this syntax:
struct ptable {
struct spinlock lock;
struct proc proc[NPROC];
};
here we are only defining a struct name ptable and there are no initialisations.
You won't find the initialization in xv6's code. Here's why.
C initializes proc's int and enum variables to 0. When ptable is implemented, struct proc proc[NPROC]; creates an array of 64 processes whose fields are initialized to 0 by the language. 0 happens to be the value of the UNUSED enumeration.
allocproc loops through ptable.proc looking for state=UNUSED, then initializes the first one it finds to all the needed values. So there's no need to explicitly initialize the structures in the array.
struct {
struct spinlock lock;
struct proc proc[NPROC];
}ptable;
It's allocated on the stack and initialized automatically. The trick here is that it's written in the GCC way, also called anonymous struct.
The struct ptable being a global variable was loaded into memory as a part of the bootmain() call during the booting of xv6 kernel. Since it is global, the default values are initialized.
Now coming to how they are actually used for storing processes and when the entries are modified.
The first process is created by userinit() which chooses an unused entry and uses it for setting up its own PCB.
After the hand crafted initproc is made by the kernel, the first call to scheduler happens which is what you are referring to here.
Since the scheduler will select a RUNNABLE process and the only one we have right now is the initproc itself, it is the one that starts running.
It makes a call to fork() for spawning a shell which calls allocproc() on each call to get another unused entry from ptable and allocate it to the process.
This is how the ptable is populated during fork and helps xv6 do a book keeping of the active processes.
I have a function that looks like this that I need to implement.
Threads call this function with these parameters. It's supposed to return with the correct time it accessed the CPU and if it can't access the CPU, it will wait until it can access it.
With regards to the correct time, I keep a global variable that gets updated each time, it calls it.
How do I implement the waits and synchronize it correctly.
int MLFQ(float currentTime, int tid, int remainingTime, int tprio)
My code looks something like this so far and it doesn't quite work.
update globalTime (globalTime = currentTime)
Mutex_lock
Add to MLFQ if needed (either to 5, 10, 15, 20, or 25 MLFQ)
if (canAccessCPU)
getCPU
unlock mutex
return globalTime
else
mutex_unlock
return MLFQ(globalTime, tid, remainingTime, tprio);
Your post uses pseudo code, and there are some ambiguities, so comment if I make the wrong assumptions here:
How do I implement the waits and synchronize it correctly[?]
Waits,
Waits in threads are often implemented in such a way as to not block other threads. Sleep() at the bottom of a thread worker function allows some time for the called thread to sleep, i.e. share time with other processes. In Windows, it is prototyped as:
VOID WINAPI Sleep(
_In_ DWORD dwMilliseconds
);
Linux sleep() here
Synchronizing:
Can be done in many ways. Assuming you are referring to keeping the order in which calls come in from several threads, you can create a simple struct that can be passed back as an argument that could contain a TRUE/FALSE indication of whether the uP was accessed, and the time the attempt was made:
In someheader.h file:
typedef struct {
int uPAccess;
time_t time;
}UP_CALL;
extern UP_CALL uPCall, *pUPCall;
In all of the the .c file(s) you will use:
#include "someheader.h"
In one of the .c files you must initialize the struct: perhaps in the
main fucntion:
int main(void)
{
pUPCall = &uPCall;
//other code
return 0;
}
You can now include a pointer to struct in the thread worker function, (normally globals are at risk of access contention between threads, but you are protecting using mutex), to get time of access attempt, and success of attempt
I am working on the implementation of system call sys_fork() on the kernel level. I did the copying of the parent process to the child process as per requirements. The problem is how to copy the child's trapframe (copy of the parent trapframe) onto the child's kernel stack to pass the assertion in mips_usermode()?
I figured out my problem.
But what I am going to say is related to OS161 system. So anyone working on this system, this will be helpful.
Ok, there is a function on the kernel side which deals with context switch. This function stores all the data related to context switch frame onto the threads kernel stack.
So all you need to do is follow the same steps and instead of switch frame structure, you need to replace it with trap frame structure.
Here is the implementation for it:-
vaddr_t stacktop;
struct trapframe *tf;
stacktop = ((vaddr_t)thread->t_stack) + STACK_SIZE; //t_stack is the kernel stack
tf = ((struct trapframe *) stacktop) - 1;
t_stack is just a chunk of memory on the kernel side to store anything related to exceptions or context switch.
Be sure to first clean out the t_stack before you load it with trapframe as it will contain data related to context switch frame incase of sys_fork implementation.
Any corrections or comments on this are welcomed.
I'm working on OS161 too. Here is how I tackle the problem.
In sys_fork, I copy parent's trapframe into a kernel heap space allocated via kmalloc:
struct trapframe* ctf = (struct trapframe*)kmalloc(sizeof(struct trapframe));
*ctf = *tf; // tf points to parent's trapframe;
Then I use thread_fork to create a child thread:
// passing address space using the second parameter of
// child_forkentry, quite dirty
thread_fork(curthread->t_name, child_forkentry, ctf, (unsigned long)as, NULL);
In child_forkentry, which is the first function called by the child, I do the following:
struct trapframe tf; // tf will be allocated on child's kernel stack
tf = *ctf
misp_usermode(&tf);
This will pass the stack check in mips_usermode.