首页
网站开发
桌面应用
管理软件
微信开发
App开发
嵌入式软件
工具软件
数据采集与分析
其他
首页
>
> 详细
辅导CPSC 415语言、讲解C++程序语言、c++编程设计调试 讲解Database|辅导留学生 Statistics统计、回归、迭代
项目预算:
开发周期:
发布时间:
要求地区:
Assignment 2
This assignment is, ideally, to be done in pairs. Working alone is strongly discouraged.
Errata
a) None so far
1 Motivation
The purpose of this assignment is to investigate process pre-emption, simple device drivers, and
synchronization through semaphores. In this assignment you will extend your kernel to pre-empt
processes, provide a simple sleep device, and implement semaphores. In doing so you will learn
about how a kernel schedules a CPU, manages shared resources, handles interrupts, and the blocking
and unblocking of process due to demdnd for resources.
2 To Start
Although the assignment is doable on your own, you are very strongly encouraged to work with
a partner. Your starting point is the kernel resulting from the first assignment. If you did not
complete assignment 1, or don’t wish to use your kernel, you may use a kernel from someone else in
the class, with their permission, or the solution kernel you will get when you clone the assignment
repo. You are also free to merge you or your partner’s assignment 1 solution with the sample
solution if you prefer. Your assignment must compile on the department Linux machines and run
in the Bochs emulator on those machines.
In this assignment you will modify the code in several modules and add a couple more modules
to extend the kernel. Note: If you use the supplied solution, the kfree() function is not
fully implemented and does not actually free memory. Consequently, don’t get too
exuberant with kmalloc() and expect kfree() to help you out.
The provided assignment 1 solution kernel compiles without any compiler warnings except for
routines in the libx directory. Your code, regardless of the kernel you start with, must also compile
without warnings except for files in the libx directory and maybe a warning about an unused
variable in ctsw.c to hold the kernel stack pointer. You are not allowed to change the command
line options to the compiler to achieve this. (You should check the sample solution to see how it
avoids having the unused variable message.)
Note: Sometimes you may see the message Clock skew detected. Your build may be incomplete.
I have never managed to figure out just what conditions cause this message to be generated, nor has
1
a problem with the build ever been observed when the message is printed. If in doubt do a make
clean and rebuild the code. You might get the message again, but it won’t produce the required
zImage file if there was an actual problem.
2.1 Assignment 2 - GIT Usage
For this assignment everyone is required to use a GIT repository on the department’s bitbucket
server to manage the source code for their assignment. Furthermore you are required to regularly
“push” your changes to the server. Consult the assignment 1 description for the basic GIT references
and commands to do this.
NOTE: You are expected to regularly check-in the work you have done throughout the assignment.
To encourage this part of the grade for this assignment will be based on a regular and
reasonable check-ins of your code. If working in pairs it is expected that both partners will contribute
to the assignment and check-in code. It is accepted that the checking in of code
may not be 50/50, but if only one person is checking in code then questions might be
raised about who is doing the work and consequently who deserves the marks.
2.2 Using your own kernel or someone else’s
If you plan on using the supplied solution kernel, skip the step in the next paragraph.
After completing the steps in the previous section copy all the modified .c, and .h files from
assignment 1 that you intend to use over-top the supplied files. Use the git add, rm (if needed),
commit and push commands to commit the changes and push them to the repository. You should
also tag this set of files as your starting point. (Check the GIT references to figure out how to do
this if you didn’t do it in A1.)
3 Objectives
You will extend the kernel from the first assignment. In your documentation you must state
which kernel you are using, be it your own, the provided solution kernel, or someone
else’s.
There are three main implementation goals for this assignment:
• To add pre-emption. (i.e. time slicing);
• To implement a simple sleep device;
• To implement semaphores.
In addition, you will need to implement a couple of auxiliary system calls to help things work
smoothly.
There are numerous ways to tackle this assignment. One way is to implement the kernel
extensions in the order they are described in this document. Another order would be to implement
section 3.3 after completing 3.4. If you use this latter approach you may want to disable pre-emption
while working on 3.3, but don’t forget to turn pre-emption back on and to re-test things.
Regardless of the order you choose, you need to test each extension before proceeding to the
next. The list below summarizes the extensions. For most of these extensions the dispatcher
2
function dispatch() in dispatch.c will require modifications and consequently it is not explicitly
mentioned.
Auxiliary System Calls: Add sysgetpid(), syskill() sysputs() in syscall.c - some useful
system calls.
Semaphore system calls: sysP() and sysV() in syscall.c - used by processes for critical section
protection.
Sempahore system - kernel side: P kern() and V kern() in semph.c - used by the kernel for
handling semaphores. The precise arguments amd details of the implementation of these
functions are left as design/implementation decisions for you to make.
Pre-emption: contextswitch() and contextinit() in - ctsw.c for timer interrupts and time
slicing.
Idle process: idleproc() in init.c - to avoid running out of processes.
Sleep system call: syssleep() in syscall.c - used by a process to sleep a specified time.
Sleep device: sleep() and tick() in sleep.c - code in the kernel for processing sleep request
and timer ticks.
Producer/consumer: producer() (really root) and consumer() in user.c - improved producer/consumer.
(Yes, it isn’t a good name choice for demonstrating semaphore usage, but
I wasn’t feeling creative.)
You may also need to update various .h files to add add constants, data structures, prototypes etc.
Such updates will not always be explicitly mentioned in the assignment description.
3.1 Auxiliary System Calls
Add the four new system calls in syscall.c with corresponding additions to disp.c. The system
call sysgetpid() returns the PID of the current process and syskill() takes a PID as an argument,
and terminates the process. To simplify things, a PID is of type PID t which is a typedef of an
unsigned int.
The prototype for sysgetpid() is
extern PID t sysgetpid( void );
The system call sysputs() is to be used by processes to perform output. Since processes can
be pre-empted, and the screen is a shared resource, access to the screen must be synchronized via
the sysputs() system call. The system call takes a null terminated string that is to be displayed
by the kernel. Since the kernel cannot be pre-empted, the kernel side of sysputs()can use kprintf
to print the string. The prototype for the system call is
extern void sysputs( char *str );
3
You can use sprintf() to create formatted strings for fancier printing. (sprintf() is like printf()
except it puts the resulting string into memory, as opposed to printing it.)
The syskill() call terminates the process identified by the argument to the call. The call
returns 0 on success. The call fails with -1 if the target process does not exist. It is okay for a
process to call kill on itself. In this case the call does not return and the process terminates. The
prototype is
extern int syskill( PID t pid );
The final auxiallary call you are to implement is syssetprio() which allows a process to set
its priority. The prototype for the call is:
extern int syssetprio( int priority );
In this system a process can have a priority from 0 to 3, with 3 being the highest priority and 0
being the lowest. Processes are created with priority 3. The result returned by this call is -1 if the
call fails for any reason (e.g. requested priority is out of range), otherwise it returns the priority
the process had prior to this call changing the priority.
There is one special case and that is priority -1. If the requested priority is -1 then then call
simply returns the current priority of the process and does not update the process’s priority.
3.2 Changes to Process Management
For this assignment you must clean-up resources that are directly associated with a process. This
includes, among potentially other things, the stack and any data that might be part of the message
passing system.
One of the resources associated with a process is its PCB and as a result it must be possible to
reuse a PCB. A side effect of this is that you will have to be careful with respect to how you assign
PIDs. A PID cannot simply be the index into the PCB table. A good PCB selection algorithm
will make it possible to index into the PCB in small, constant time while still making it easy to
determine whether or not a PID is valid. In addition, to minimize the problems with process
interactions based on PIDs, the PID reuse interval needs to be as large as possible.
In assignment 1 if a process runs off the end of its code (i.e. doesn’t call sysstop() and just
keeps going) or executes the C language return statement bad things happen. (If you don’t believe
this just try adding a return to one of your processes in assignment 1.) In this assignment you are
to modify the process creation code so that if a process does a return, either explicitly through th
process to receive from is blocked on a send to this process return statement or by running off the
end of its code, the process is cleaned up appropriately and the process/system does not crash or
restart.
There are a couple of ways to do this. One is to set-up the stack of the process so that
when the process “returns” it transfers control to sysstop(). (i.e. set-up the stack so that the
spot that contains the return address for the function call now contains the address of sysstop().)
Alternatively you could introduce a “wrapper” process. This process takes as an argument the
function address passed in as the argument to syscreate(). Now each time a process is created
the wrapper process is what is created. The body of this process will call the real process (i.e.
the address of the user function passed to syscreate()), and when the “user process” returns, the
“wrapper process” will call sysstop(). For this approach to work, when the wrapper process
4
is created the stack will have to be constructed such that when the wrapper process runs it can
retrieve the address of the user function to “call” (i.e. run). That means the arguments will have to
be placed in the appropriate place of this initial stack. It should be noted that with both of these
approaches no assembly language code is required. You may choose either approach or develop
something of your own.
3.3 Semaphore Subsystem
This section describes a semaphore system for our Xeros kernel. In a general purpose semaphore
system there are normally calls to create and destroy semaphores. In this system that will not be
the case. The kernel will initially create and initialize two semaphores identified by the integer
values 0 and 1. At the kernel implementation level, each semaphore has associated with it an
integer value and a queue of processes blocked and waiting for that semaphore. The initial value
of the integer associated with a semaphore is 0. This integer is called the semaphore value. The
semaphore behaviour is as follows:
• Processes wanting to acquire a semaphore use the sysP() call. If the semaphore value is
0 the process is blocked on this semaphore. If the semaphore value is greater than 0. The
semaphore value is decremented by 1 and the process is not blocked.
• A process can “release” a semaphore by calling sysV(). The process calling sysV() is not
blocked. When sysV() is called, if there are processes blocked on the semaphore, the highest
priority process is unblocked and the semaphore value remains unchanged. If more than one
process of the same priority is waiting for a semaphore the first one makeing the sysP() is to
get the sempahore. If there are no processes blocked on the semaphore the semaphore value
is incremented by 1.
• the above actions are atomic at the kernel level.
The normal behaviour is that a process makes a corresponding sysV() to release an “acquired”
semaphore, but that is not a requirement. This implies that the sysV() call does not need to be
made by the process acquiring a semaphore. (Observe that some process without the semaphore
will initially have to call sysV() to get things going otherwise every call to sysP() will block.
Additionally, this semaphore implementation can be turned into a counting semaphore version by
making extra sysV()) calls.
3.3.1 The sysP() and sysV() Calls
To support the semaphore system two system calls are introduced and their specifications follow.
extern int P(int semaphore);
This function returns 1 on success and 0 on failure. The argument identifies which of the two
semaphores the sysP() operation applies to. If the parameter is invalid 0 is returned otherwise 1
is returned. If the value of the semaphore is 0 when sysP() is processed then the process blocks.
If the value is greater than 0 then the value is decremented by 1 and the process is not blocked.
extern int V(int semaphore);
5
This function returns 1 on success and 0 on failure. The argument identifies which of the two
semaphores the sysV() operation applies to. If the parameter is invalid, 0 is returned otherwise 1
is returned. If, when the call is made, there are processes blocked on the semaphore the highest
priority process is unblocked (if there are multiple processes of the same priority that can be
unblocked the one that called sysP() first is to be selected). If no processes are waiting on the
semaphore, the value is simply incremented. The process calling sysV()is not blocked.
3.4 Pre-emption
In order to pre-empt a process the kernel must get control of the processor. Since the currently
running process implicitly has control of the CPU, a hardware timer interrupt is used to transfer
control to the kernel on a regular basis. In order to make our kernel pre-emptive, four steps need to
be performed. First the context switcher needs to be modified to handle both the hardware timer
interrupt, and the software system call interrupt; this is outlined in Figure 1. This figure is a broad
outline of the processing and does not identify precisely what needs to be done and how.
ContextSwitcher
push kernel state onto the kernel stack
save kernel stack pointer
switch to process stack
save return value of system call
pop process state from the process stack
iret
timer entry point:
disable interrupts
push process state onto process stack
save some sort of indication that this is a timer interrupt
jump to common entry point
syscall entry point:
disable interrupts
push process state onto process stack
save some sort of indication that this is system call
common entry point:
save process stack pointer
switch to kernel stack
pop kernel state from kernel stack
recover entry point type to determine return type.
return from context switcher
Figure 1: The new context switcher.
Immediately after an interrupt occurs, the interrupts need to be disabled since we are not
building a re-entrant kernel. (In the set evec() code provided, when the interrupt vector is set-up,
a trap gate is specified instead of an interrupt gate so disabling interrupts isn’t done automatically.
6
Note that the context switcher draft 2 assumes that the interrupts are automatically disabled.
However, a couple of slides later it is pointed out that one might have to disable interrupts explicitly.)
As a result, this means that interrupts need to be deferred while the kernel is running. Consequently,
the first instruction executed by the ISR needs to disable interrupt checking by the CPU. The cli
instruction does this. It is not necessary to re-enable interrupts before the iret is performed
because the restoration of the saved eflags register will have the interrupt bit appropriately set.
If you forget to do this you probably will not encounter any problems, or at least very rarely. It
turns out that if you don’t do the cli there is about a 5 instruction window where problems could
occur if a hardware interrupt goes off. (Think about why the window is so small or what would
cause the window of vulnerability to close after about 5 instructions.)
Since each interrupt has its own vector (i.e. address of the ISR code to jump to), we can easily
distinguish between a system call trap and a hardware interrupt, like the timer, by having different
ISRs. Ideally we want to reuse as much code as possible between the hardware interrupt and system
call trap processing code. A hardware interrupt, unlike an exception used for a system call, requires
that ALL registers contain what they had before the interrupt so special care needs to be taken.
Although which registers can be clobbered during the kernel’s processing of a system call is
design specific, it is the case that the return value of the system call will be in register eax. If the
context switcher returns the value of a system call in eax, and the same code is used to return from
an interrupt (which should be the case) then the return value of a hardware interrupt needs to be
the value of eax when the interrupt occurred.
To have hardware interrupts fit in with our context switch model, one approach is to make a
hardware interrupt appear like a system call. To do this the context switcher needs to return an
indication to the dispatcher that a timer interrupt occurred. It is recommended that you define a
TIMER INT request identifier along with all the other system call request identifiers that a dispatcher
can receive and have that returned by the context switcher when a timer interrupt occurs.
Next, modify the dispatcher to handle the TIMER INT request. This request should do the same
thing as the YIELD request: place the current process at the end of the ready queue, de-queue the
next process in the ready queue, and run it. In the next section you will have to add an additional
line of code to this request handler, so don’t share it with the yield code.
Third, change the initial flags being set by the context initializer in create.c. Instead of 0,
the interrupt bit should be enabled, i.e., the initial flags should be 0x00003200. (The 0x3000 will
prevent processes from accessing privileged I/O space, not that that should be an issue.)
Finally, you must add a set evec() call as part of configuring the timer interrupt. The timer
is on IRQ0 which translates to interrupt number 32. Make sure the handler is the timer entry
point in the context switcher and not the syscall entry point. You will also have to initialize the
timer and enable the corresponding interrupt on the PIC (i8259). To do this use the initPIT(
divisor ) function. This function takes one argument, the rate at which the timer should trigger
the interrupt. For example, specifying a value of 100 will mean that the interrupt will be triggered
every 100th of a second (every 10 milliseconds). Thus, a process will be given a time slice of 10
milliseconds, before being pre-empted. This is the recommended time slice for the system. Thus,
after the call to set evec() you should add
initPIT( 100 );
When a timer interrupt occurs, the hardware has to be re-armed in order for the interrupt
to occur again. This is done by signalling an end of interrupt (EOI). To signal an EOI use the
7
provided function end of intr(). This function should be called, probably from dispatch() as
the last operation in the servicing of the TIMER INT request. You will notice very quickly if you
forget to do this.
3.4.1 Priorities
In this system processes have priorities and the scheduling policy is that higher priority processes
(i.e. higher priority number) are always run first and that round-robin scheduling is used within a
priority. You are not to add kernel support to avoid CPU starvation (i.e. priorities are not adjusted
by the kernel based on how much CPU time a process has received) or to deal with the priority
inversion problem. If you are using the supplied kernel, observe that the code to manage the ready
queue is in disp.c and that there is a single queue. Consequently, you will need to modify the queue
management routines to support multiple ready queues. It is okay to change the next() and ready()
functions to take additional arguments, perhaps the number of the queue they are working on.
3.5 Idle Process
Since processes might become blocked for one reason or another, such as waiting to acquire a
semaphore or a timer to expire, among other things. Consequently it is important to ensure that
there is at least one ready process available at all times. As you know, bad things happen if the
kernel runs out of processes to schedule. In order to prevent this, you should create an idle process.
Its only job is to be ready and is simply an infinite loop. You will need to modify the dispatcher
so that the idle process will run only if no other process is available. Instead of just having the
idle process execute an infinite loop with an empty body, you might want to try executing the hlt
instruction in the body of the loop. (You don’t have to use hlt, it is just something you can try if
you like.)
The prototype for the process should be
extern void idleproc( void );
and it should be created by initproc(). You should start with a simple idle process before trying
to use hlt, if you choose to try hlt. If you ever schedule the idle process, and don’t have the timer
interrupt working, control will never return to the kernel.
3.6 Sleep Device
The sleep device will allow processes to sleep for a requested number of milliseconds. First, add
a system call syssleep() to syscall.c that takes one unsigned int parameter and returns an
unsigned int. The single parameter is the number of milliseconds the process wishes to sleep. For
example, to sleep for a second the call would be syssleep(1000); The prototype for the calls is
extern unsigned int syssleep( unsigned int milliseconds );
and should be added to xeroskernel.h. The return value for syssleep() is 0 if the call was
blocked for the amount of time requested otherwise it is the amount of time the process still had
to sleep at the time it was unblocked. In this assignment there is no mechanism to unblock the
process early, but in assignment 3 there will be. So, at this point one would always expect to get
back 0, although in A3 that won’t be the case.
8
Modify the dispatcher appropriately to recognize the system call, and have it call the corresponding
kernel sleep() function that you will write. To the TIMER INT request service code add
a call to a function called tick(). This function will also be written by you and it notifies the
sleep device code that a clock tick has occurred. Since Bochs is an emulator, it is sometimes the
case that the machine the emulator is running on can be emulated faster than the original machine
actually ran. As a result, time will pass faster on these machines than in “real” or “wall clock”
time. An attempt has been made to minimize this problem through configuration options in the
bochsrc file in the xeros directory, but don’t be overly concerned if things do not match wall clock
time.
To implement the kernel side of syssleep(), implement in sleep.c, the two functions sleep()
and tick(). The sleep() function places the current process in a list of sleeping processes such
that its place in the list corresponds to when it needs to be woken. Observe that the clock tick has
a certain granularity and that the parameter to syssleep() has a finer granularity than a clock
tick. To deal with this problem the kernel will treat the value of the parameter to syssleep() as the
minimum amount of time to sleep for and wake a process on the next clock tick after this minimum
amount of time has elapsed. To simplify the managing of the times and the sleep queue it would
be a good idea to convert, within the kernel, the amount of time to sleep into “clock ticks” (time
slices) and work from there. (Example conversions: 10ms = 1 tick, 11ms = 2 ticks, 0 ms = 0 ticks,
1ms = 1 tick etc). As an example, assuming times have been converted to ticks, if there are three
processes in the sleep list, that need to be woken after 5, 7, and 8 time slices respectively, and the
current process wants to sleep for 6 time slices, then it should be placed second in the sleep list.
You should try to come up with an efficient way of doing this. (Hint: delta list). The process is
NOT to be placed on the ready queue until it is woken. After the sleep() call completes, the
dispatcher selects the next process in the ready queue to run. Don’t confuse the behaviour of the
kernel sleep() function with that of syssleep(). The sleep() function always returns to the
dispatcher, even though a process is being blocked but the sysleep() call won’t return to the
application until at least the specified time has elapsed.
The tick() kernel function notifies the sleep device that another time slice has occurred. The
function should wake up any processes that need to be woken up by placing them on the ready
queue associated with their priority, marking them as ready, and updating internal counters.
3.7 Some Testing - Demonstrating Semaphores and Priorities
At this point you should have a pre-emptive multitasking kernel and you should have extensively
tested it. To demononstrate some of the system’s functionality implement processes with the
following behaviour:
Create a root process that:
• Prints a message saying it is alive.
• Calls sysV() on semaphore 0.
• Creates 4 processes and as each process is created prints a message saying it has created a
process and what that process’s PID is.
• It then loops and calls sysP() on semaphore 1 four times.
• It then prints ”Got 4 semaphores” Each of these processes does the following:
9
– Prints a message indicating it is alive and its PID.
– Sleeps for 5 seconds.
– Attempts to acquire semaphore 0.
– Prints a message saying it got the semaphore and then sleeps for 3 seconds.
– Prints a message saying sleeping has stopped and it is going to release the semaphore.
– Calls sysV() on both semaphores 0 and 1.
– Runs off the end of its code.
• The root process next sleeps for 4 seconds.
• The root process sets its priority to 0.
• The root process calls sysP() on semaphore 0. At this point the root process “has” both
semaphores.
• The root process sets a global variable “priority” to 0.
• The root process creates 4 processes.
• The root process then performs the following block of actions 4 times:
– Call sysV() on semaphore 0.
– Call sysP() on semaphore 1.
– Increments the global priority variable by 1.
• The root process prints a message that it is done an calls syskill() on itself.
• Each of the created processes does the following:
– Prints a message indicating what it’s PID is.
– Calls sysP() on semaphore 0.
– Reads the priority value from the global variable “priority”.
– Prints the “My priority will be” followed by the priority value.
– Sets its priority to the indicated priority.
– Calls sysV() on semaphore 1
– Prints 10 times, via a loop “Loop i” where is the iteration number.
– The process then exits by running off the end of its code.
Note - In the above each line of printing is to be on a line by itself and start with ”Process
xxx” where xxx is the process ID of the process before acutualling printing the message specified
above. The times are to be the actual values provided as a parameter to the sleep system call. (i.e
the times you specify to sleep should be the actual ones that would be used if the timer on the
emulator accurately captured wall clock elapsed time. You do not have to try to select sleep values
that result in the process actually sleeping that amount of time according to the “wall clock.” )
10
4 Write-up
Your write-up is to be a well organized, typed document containing testing and assignment questions
sections as described in the next sections.
4.1 Testing Documentation
Your testing documentation is to a plain text ASCII file with the name Testing.txt. A file with this
name has been provided for you.
You are to handin test output for the 6 test scenarios outlined below. Each test scenario is to
be different (i.e. exercises a different code path in the kernel.)
1. One test demonstrating that time-sharing is working.
2. Two tests of syskill();
3. Two tests showing that priorities work.
4. One test showing the priority inversion problem.
The documentation is to organized in sections, with each section corresponding to one test.
The sections are to organized in the order of the tests described above. Each section is to indicate
the purpose of the test and provide a brief description of how the test was performed (i.e. what
was the scenario, how it was actually done.) You are then to provide a sample run for the test. It
is acceptable to remove large areas of repeated data provided you indicate this and you may also
annotate the output. Finally, indicate if this test was a pass or a fail. Note that it acceptable for
your program to fail a test. In such a case you will get full marks for the testing portion of the
assignment but may not get full marks in the implementation part of the assignment.
4.2 Some Questions to ponder
You do not have to hand in the answers to these questions. However they are representative of the
types of questions you might get on an exam.
1. In the implementation section, the instructions have the use of set evec() occurring before
the call to initPIT(). Could the order of these two calls be reversed (i.e. could initPIT()
be called before set evec()? Carefully explain/justify your answer.
2. Currently the function signature for new processes is such that the function takes no arguments.
Suppose that the syscreate() call were changed so that all functions passed to
syscreate() expect two arguments. The function being passed to create now has the form:
function(int argc, int** argv)
Draw and label as precisely as possible a picture of what a process’s stack would look like when
it is put onto the ready queue for the first time. The picture you draw is to assume that the
changes required by the “Changes to Process Management” section have been implemented.
11
5 How the TA’s will Test Your Code
To test your code, the TA’s will do three things. First, they will compile and run your code as is,
verifying that the testing scenario above seems to work. Second, they may replace your user.c
with ours, recompile the code and run it. Our code will test your kernel. Third, the TA’s will
visually inspect your code and meet with you to discuss your assignment.
6 What to Hand In
Handing in is done by simply making sure that your git repository with all appropriately committed
changes has been pushed to the Stash server. You will be marked on the last push to the main
branch of the repo and any late penalty computed will be based on the time of that push. If you
have added any files to your code base don’t forget to add them to the repo before doing your
commit and push. It can
软件开发、广告设计客服
QQ:99515681
邮箱:99515681@qq.com
工作时间:8:00-23:00
微信:codinghelp
热点项目
更多
代写math 1151, autumn 2024 w...
2024-11-14
代做comp4336/9336 mobile dat...
2024-11-14
代做eesa01 lab 2: weather an...
2024-11-14
代写comp1521 - 24t3 assignme...
2024-11-14
代写nbs8020 - dissertation s...
2024-11-14
代做fin b377f technical anal...
2024-11-14
代做ceic6714 mini design pro...
2024-11-14
代做introduction to computer...
2024-11-14
代做cs 353, fall 2024 introd...
2024-11-14
代做phy254 problem set #3 fa...
2024-11-14
代写n1569 financial risk man...
2024-11-14
代写csci-ua.0202 lab 3: enco...
2024-11-14
代写econ2226: chinese econom...
2024-11-14
热点标签
mktg2509
csci 2600
38170
lng302
csse3010
phas3226
77938
arch1162
engn4536/engn6536
acx5903
comp151101
phl245
cse12
comp9312
stat3016/6016
phas0038
comp2140
6qqmb312
xjco3011
rest0005
ematm0051
5qqmn219
lubs5062m
eee8155
cege0100
eap033
artd1109
mat246
etc3430
ecmm462
mis102
inft6800
ddes9903
comp6521
comp9517
comp3331/9331
comp4337
comp6008
comp9414
bu.231.790.81
man00150m
csb352h
math1041
eengm4100
isys1002
08
6057cem
mktg3504
mthm036
mtrx1701
mth3241
eeee3086
cmp-7038b
cmp-7000a
ints4010
econ2151
infs5710
fins5516
fin3309
fins5510
gsoe9340
math2007
math2036
soee5010
mark3088
infs3605
elec9714
comp2271
ma214
comp2211
infs3604
600426
sit254
acct3091
bbt405
msin0116
com107/com113
mark5826
sit120
comp9021
eco2101
eeen40700
cs253
ece3114
ecmm447
chns3000
math377
itd102
comp9444
comp(2041|9044)
econ0060
econ7230
mgt001371
ecs-323
cs6250
mgdi60012
mdia2012
comm221001
comm5000
ma1008
engl642
econ241
com333
math367
mis201
nbs-7041x
meek16104
econ2003
comm1190
mbas902
comp-1027
dpst1091
comp7315
eppd1033
m06
ee3025
msci231
bb113/bbs1063
fc709
comp3425
comp9417
econ42915
cb9101
math1102e
chme0017
fc307
mkt60104
5522usst
litr1-uc6201.200
ee1102
cosc2803
math39512
omp9727
int2067/int5051
bsb151
mgt253
fc021
babs2202
mis2002s
phya21
18-213
cege0012
mdia1002
math38032
mech5125
07
cisc102
mgx3110
cs240
11175
fin3020s
eco3420
ictten622
comp9727
cpt111
de114102d
mgm320h5s
bafi1019
math21112
efim20036
mn-3503
fins5568
110.807
bcpm000028
info6030
bma0092
bcpm0054
math20212
ce335
cs365
cenv6141
ftec5580
math2010
ec3450
comm1170
ecmt1010
csci-ua.0480-003
econ12-200
ib3960
ectb60h3f
cs247—assignment
tk3163
ics3u
ib3j80
comp20008
comp9334
eppd1063
acct2343
cct109
isys1055/3412
math350-real
math2014
eec180
stat141b
econ2101
msinm014/msing014/msing014b
fit2004
comp643
bu1002
cm2030
联系我们
- QQ: 9951568
© 2021
www.rj363.com
软件定制开发网!