C++代写 | 操作系统OS | CS300 Project 6
Project 6: System Calls
1. Overview
To complete this assignment, it is vital that you have carefully completed and understood the content in the following guides
You can work in a group with up to three-four students. You can also work alone, but you get no extra credits for this.
2. Array Statistics Sys-Call
First, you’ll add a new system call that computes some basic statistics on an array of data. In practice, it makes little sense to have this as a sys-call; however, it allows us to become familiar with accessing memory between user and kernel space before accessing other kernel data structures.
Requirements
Create a cs300/
in ther kernel folder, create a file named array_stats.h
with the contents:
// Define the array_stats struct for the array_stats sys-call.
#ifndef _ARRAY_STATS_H_
#define _ARRAY_STATS_H_
struct array_stats{
long min;
long max;
long sum;
};
#endif
Create a new sys-call named array_stats
(function sys_array_stats()
):
- Implement it in your kernel’s
cs300/
folder in a file namearray_stats.c
. Use#include "array_stats.h"
- Make it sys-call number 341 (in syscall_64.tbl).
- The sys-call’s prototype is:
asmlinkage long sys_array_stats( struct array_stats *stats, long data[], long size);
stats
: A pointer to onearray_stats
structure allocated by the user-space application. Structure will be written to by the sys-call to store the minimum, maximum, and sum of all values in the array pointed to bydata
.data
: An array oflong int
values passed in by the user-space application.size
: The number of elements indata
. Must be > 0.
The array_stats
sys-call must:
- Set the three fields of the
stats
structure based on thedata
array. The values indata
are signed (positive or negative). Nothing special need be done if the sum of all the data overflows/underflows along
. - Return 0 when successful.
- Returns
-EINVAL
ifsize <= 0
. - Returns
-EFAULT
if any problems accessstats
ordata
. - You must not allocate or use a large amount of kernel memory to copy the entire input
data
array into.
Hints
- Use
printk()
calls in the kernel to print out debug information. You may leave some of theseprintk()
messages in your solution as these messages are not technically displayed by the user-space application. The messages you leave in should be helpful such as showing parameters values or errors it caught; they should not be of the sort “running line 17”, or “past loop 1”. It is usually useful toprintk()
the parameters you are given, andprintk()
any error conditions you handle. - Correct memory access is the hardest part of writing this sys-call.
- The kernel cannot trust anything it is given by a user-level application. Each pointer, for example, could be: a) perfectly fine, b) null, c) outside of the user program’s memory space, or d) pointing to too small an area of memory. Since the kernel can read/write to any part of memory, it would be disastrous for the kernel to trust a user-level pointer.
- Each read you do using a pointer passed in as input (a user-level pointer) should be done via the
copy_from_user()
macro. This macro safely copies data from a user-level pointer to a kernel-level variable: if there’s a problem reading or writing the memory, it stops and returns non-zero without crashing the kernel.- First use this macro to copy values into a local variables (which are in the kernel’s memory space). Then, use these local variables in your program. Use
copy_from_user()
to copy one value at a time from the user’sdata
array into this variable. If a copy fails (copy_from_user()
returns non-zero) then have your sys-call end immediately and return-EFAULT
. - Hint: Double check that you only ever access the
data
array usingcopy_from_user()
! - You can directly access
size
because it is passed by value so there is no possible problem access memory.
- First use this macro to copy values into a local variables (which are in the kernel’s memory space). Then, use these local variables in your program. Use
- Likewise, when writing to a user-level pointer, use
copy_to_user()
which checks the pointer is valid (non-null), inside the user-program’s memory space, and is writable (vs read-only).- Hint: Create a local variable of type
struct
array_stats
inside your sys-call function. Compute the correct values in thisstruct
first, then at the very end usecopy_to_use()
to copy the contents to user’s pointer.
- Hint: Create a local variable of type
- The kernel is compiled with C90; you must declare your variables at the start of a block (such as your function) vs in the middle of your function.
- A user-space test program is provided to extensively test your system call. Your sys-call implementation should pass all of these tests. It is likely that your code will be marked based on passing these (and perhaps other) tests. You may want to run the tests one at a time by commenting out calls in
main()
.
3. Process Ancestor Sys-Call
In this section, you’ll implement a sys-call which returns information about the current process, plus its ancestors (its parent process, it’s grandparent process, and so on).
Requirements
- In the kernel’s
cs300/
folder, create a file namedprocess_ancestors.h
with the contents:
// Structure to hold values returned by process_ancestors sys-call
#ifndef _PROCESS_ANCESTORS_H
#define _PROCESS_ANCESTORS_H
#define ANCESTOR_NAME_LEN 16
struct process_info {
long pid; /* Process ID */
char name[ANCESTOR_NAME_LEN]; /* Program name of process */
long state; /* Current process state */
long uid; /* User ID of process owner */
long nvcsw; /* # voluntary context switches */
long nivcsw; /* # involuntary context switches */
long num_children; /* # children process has */
long num_siblings; /* # sibling process has */
};
#endif
- Create new sys-call named
process_ancestors
(functionsys_process_ancestors()
):- Implement it in your kernel’s
cs300/
folder in a file nameprocess_ancestors.c
. Use#include "process_ancestors.h"
- Make its sys-call number 342 (in
syscall_64.tbl
). - The sys-call’s prototype is:
asmlinkage long sys_process_ancestors( struct process_info info_array[], long size, long *num_filled)
info_array[]
: An array ofprocess_info
structs that will be written to by the kernel as it fills in information from the current process on up through its ancestors.size
: The number of structs ininfo_array[]
. This is the maximum number of structs that the kernel will write into the array (starting with the current process as the first entry and working up from there). Thesize
may be larger or smaller than the actual number of ancestors of the current process: larger means some entries are left unused (seenum_filled
); smaller means information about some processes not written into the array.num_filled
: A pointer to along
. To this location the kernel will store the number of structs (ininfo_array[]
) which are written by the kernel. May be less than size if the number of ancestors for the current process is less thansize
.
- Implement it in your kernel’s
- The
process_ancestors
sys-call must:- Starting at the current process, fill the elements in
info_array[]
with the correct values.- Ordering: the current process’s information goes into
info_array[0]
; the parent of the current process intoinfo_array[1]
; grandparent intoinfo_array[2]
; and so on. - Extra structs in
info_array[]
are left unmodified.
- Ordering: the current process’s information goes into
- Return 0 when successful.
- Returns
-EINVAL
ifsize <= 0
. - Returns
-EFAULT
if any problems accessinfo_array
ornum_filled
. - You must not allocate or use a large amount of kernel memory to copy/store large arrays into.
- Starting at the current process, fill the elements in
- You must also create a user-space test program which calls your sys-call and exercises its functionality. You must do at least some testing on to show it works correctly, and that it generates correct error values in at least a few of the failure conditions (bad pointers, …).
- Hint: Use asserts in your test code.
- We will have an extensive test suit to exercise your solution!
Hints
- You will make extensive use of the kernel’s
task_struct
structures: each process has atask_struct
. You can find thetask_struct
for the current process using the macrocurrent
.- For example, the PID for the currently running process on this CPU can be found with:
long pid = current->pid;
- For example, the PID for the currently running process on this CPU can be found with:
- Basic algorithm sketch:
- Start from current process and fill in fields for
info_array[0]
. - Move to parent of this process (
current->parent
), and copy its info intoinfo_array[1]
. - Repeat until the parent of the process you are working on is itself (
cur_task->parent == cur_task
).- The first task spawned by Linux is its own parent, so hence its
parent
pointer points to itself. This process has PID 0 and is the idle task (named swapper). - I recommend that you first get the info on the current process and print it to the screen (
printk
) to ensure you have the correct values. Then work on getting the data into theprocess_info
structs and handling ancestors.
- The first task spawned by Linux is its own parent, so hence its
- Start from current process and fill in fields for
- Hints on the fields of
process_info
:- Quite a few of the values can be pulled directly out of the
task_struct
structure. Look for fields with a matching name.task_struct
is defined ininclude/linux/sched.h
(in your kernel folder). To include this in your sys-call implementation use:#include <linux/sched.h>
- The name of the program for a process is stored in the
task_struct.comm
field. - The user ID for the owner of a process can be found inside the process’s credentials (
cred
field). Insidecred
, you want to look at theuid
field. - For counting the number of children and siblings, you’ll want to start with the following linked list nodes:
task_struct.children
, andtask_struct.sibling
.- These are nodes in circular linked lists. Linux uses the struct
list_head
for a node because in a circular linked list, each node can be thought of as the head of the list. - You can follow the next field of a node in the list (a
list_head
) to get the node (list_head
) of the next element in the list. - It is a circular linked list, so you’ll have to determine how to count the number of elements (think of how you know when to stop following next pointers). Hint: Think of addresses.
- Note that Linux has some clever (complicated) ways of taking a node in the list (which just has a
next
andprev
field pointing to otherlist_head
structures) and accessing the full structure that contains the node. For example, given alist_head struct
that is in atask_struct
, the kernel includes macros to give you the fulltask_struct
! However, you have (mercifully) been spared having to do this. If you are interested, for fun try printing out the PID of each of the sibling processes.
- These are nodes in circular linked lists. Linux uses the struct
- Quite a few of the values can be pulled directly out of the
- Safe memory access is critical. Apply all the suggestions from the previous sys-call for safe memory access.
- You can use the
ps
command in your QEMU virtual machine to display information on running processes and verify the sys-call output. See ps’s man page for how to select the information it displays.
4. Deliverables
In CourSys, create a group for your submission. Even if you worked alone, you need to be in a group (of 1 in this case) to submit. Submit the following to CourSys:
- A patch of your Linux kernel folder for the files you changed. Patch must be done in accordance with the Kernel Patch guide (available here). Must patch cleanly (either using
Git
orpatch
). - An archive file (tar.gz) of your kernel code:
– cs300/ folder
– /makefile, syscall_64.tbl - An archive file (tar.gz) of your user mode folder containing your test code for the sys-calls. Must include a makefile with a target “transfer” to copy the statically linked executables to QEMU.
May include the providedarray_statistics
test code.* Please remember that all submissions will automatically be compared for unexplainable similarities. * Marking will be done on a 64-bit system. If you did your development on a 32-bit system, your code must still compile and run perfectly on a 64 bit system. Specifically, double check that you have: 1. Created the necessary rows in `arch/x86/syscall/syscall_64.tbl`
These will allow us to call your functions, but the numbers must match exactly those numbers listed in this assignment. (In 32-bit the numbers differ from 64bit). 2. Your user-level code uses the correct 64-bit sys-call numbers.
Hint: Make your user-level code check the GCC defined constant_LP64
and automatically pick the correct sys-call number:#include <stdio.h> #include <unistd.h> #include <sys/syscall.h> #if _LP64 == 1 #define _CS300_TEST_ 340 #else #define _CS300_TEST_ 390 #endif int main(int argc, char *argv[]) { printf("\nDiving to kernel level\n\n"); int result = syscall(_CS300_TEST_ , 12345); printf("\nRising to user level w/ result = %d\n\n", result); return 0; } ```
5. Resources
- Created/updated by Brian Fraser and Mohamed Hefeeda.