contributed by <johnnylord
>
sysprog2019
As the docs said, q_insert_tail
and q_size
should operate in time O(1), I need to add additional field to track the tail and the size of queue.
Create a new, empty queue.
When construct or new a brand new queue, make sure you initialize all the fields in it. Otherwise, there is a big chance that other functions(e.g q_insert_head
, q_insert_tail
, etc) might use the wrong value of these fields.
Free all storage used by a queue
As the space of the string of each list element is allocated from the heap, I have to free the space of string before freeing the list element. Otherwise, there will be lots of memory leak in the heap space.
Attempt to insert a new element at the head of the queue (LIFO discipline).
Thanks to the static analysis feature provided by cppcheck
, it helps me find sections that cause memory leak. Because I've already allocated some space at Line 12 in the source code, I need to free the space if there is any exception occurs in the following section.
Attempt to insert a new element at the tail of the queue.
There is no big difference between q_insert_tail
and q_insert_head
. Just be careful when dealing with the update of the linked list. As I've kept the information of tail of the queue in q->tail
, I can append the new element directly next to q->tail
without traversing the queue all the way from q->head
to the end of the queue.
Attempt to remove the element at the head of the queue.
Just write the code as the docs said. Easy~
Compute the number of elements in the queue.
As I keep the size
information in the queue data structure itself, I don't have to traverse the queue and count the element. Just return the size information of the queue. Easiest part~~
However, I think the return type of this function should be unsigned int
. The size of the queue won't be negative.
Reorder the list so that the queue elements are reversed in order.
Here is the entry point of qtest
. First of all, do associated action based on the options and arguments user type in.
Reference
$ man 3 getopt
Then, it does some essential initialization(e.g queue, console, etc).
Use
sigaction
instead ofsignal
as the docs said below
Portability
The only portable use of signal() is to set a signal's disposition to SIG_DFL or SIG_IGN.
The semantics when using signal() to establish a signal handler vary across systems (and POSIX.1 explicitly permits this variation); do not use it for this purpose.
POSIX.1 solved the portability mess by specifying sigaction(2), which provides explicit control of the semantics when a signal handler is invoked; use that interface instead ofsignal().
Reference
man 2 signal
> Section 'Portability'
Here's the data type of some variables used in init_cmd
. As they are all static
variable, these variables are only visible in console.c
.
We can see that the type of cmd_list
is cmd_ptr
. And cmd_ptr
is an alias of struct CELE*
. When look into the struct CELE
further, it's a one way linked-list, and each element stores the information of its command metadata(e.g name
and documentation
) and its associated function's address.
Here is the circumstance that we can do sorting on the linked-list(if the
cmd_list
is out of order), as thecmd_list
is sorted in alphabetical order. Jserv teacher asked student, "Why we need to do sorting on linked-list?"
Let's take a look at how add_cmd
work…
Here is a interesting part, the function use double pointer, cmd_ptr*
, to keep track of the last location(either the address of head of the list or the address of next
field inside the structure). In this way, the code can be written in a more general way.
If I implements this, I might write the code in this way… I have to use if
statement to determine the last location, and take associated action. (Not general)
The technique that it used to deal with the I/O is something called RIO(Robust I/O). RIO is a buffered IO. One of the advantage of buffered I/O is that it can decrease the number of time it calls system call(e.g read, write, etc)
Here is a simple demo that show the difference when we used buffered I/O and unbuffered I/O.
I use strace
tool to analyze the system call the program calls. If I don't buffered the input, like nonbuffered.c
, there will be lots of I/O system call.
The more system calls, the more context-switch. And in consequence, slow down the program.
Here is the data structure of RIO.
In the process, all the opened test files will have their own RIO data structure. fd
will keep the file descriptor of the file, and buf
is the buffer for its file.
And here is how the program utilize this data structure
Basically, this function will contiuously read content from the file until it encounter \n
or reach EOF
…
And then it take out each character in the buffered, and process then.
After reading a line, we need to interpret the line. The process will then call some functions in order ...
-> parse_args
-> interpret_cmda
-> ...
.
The parse-args
will transform the line into a list of arguments. And send the number of arguments(argc
) and the list of arguments(argv
) to interpret_cmda
for further processing.
Inside interpret_cmda
…
We can see that it will try to find the function named argv[0]
in the cmd_list
, and call the function. If the return value or the value of ok
is not true, then it will update the global variable err_cnt
in record_error()
.
Then it will check if there is any error(err_cnt
should be 0
) during the process and return associated value.
Finally, the process return 1
if there is any error that occurs during the process, and 0
otherwise.
By seeing the Makefile
, I can tell that it executes the driver.py
script in the scripts/ folder.
Thte Tracer
stores the information about each test case, and store the score we can get.
This script will try to run each test case and figure out the score we get.
gdb
to trace the code and analyze the behaviour during the processInspired by ztex's hackmd
valgrind
to analyze memory usageInspired by Naetw's hackmd