SER 334

Réussis tes devoirs et examens dès maintenant avec Quizwiz!

A-0 B-2603 C-2603 D-2600

From the program below, identify the values of the pids at lines A, B, C, and D. (Assume that the actual pids of the parent and child are 2600 and 2603, respectively. Also assume that fork will always succeed.) int main() { pid_t pid, pid1; /* fork a child process */ pid = fork(); if (pid < 0) { /* error occurred */ fprintf(stderr, "Fork Failed"); return 1; } else if (pid == 0) { /* child process */ pid1 = getpid(); printf("child: pid = %d", pid); /* A */ printf("child: pid1 = %d", pid1); /* B */ } else { /* parent process */ pid1 = getpid(); printf("parent: pid = %d", pid); /* C */ printf("parent: pid1 = %d", pid1); /* D */ wait(NULL); } return 0; }

a. A benefit of synchronous communication allows communication between sender and receiver. A disadvantage would be with a non-blocking or asynchronous communication where there is no wait for a signal, work just continues. Therefore communication from sender and receiver is not established. b. With fixed-sized messages we know how big the message is and how much space we need for it. With variable-sized we have more flexibility and also provides the benefit of only needing one message to be sent. For instance if a fixed-size message is too long we would have to send more than one message. When programming the user must take into consideration how the system handles message passing to configure using fixed-sized or variable sized messages.

What are the benefits and disadvantages of each of the following? Consider both the system level and programming level. Synchronous and asynchronous communication. Fixed-sized and variable-sized messages.

Running two things at once. Two threads can run at the same time

Parallism

Each request could be placed in a queue within the server, these RPC calls are being delegated by the CPU on the server to determine which respective stubs it must invoke based on the matchmaker (port to listen to and handle data communication between client and server). The packet sent from client to server will give us all pertinent header data to determine which operation is to be invoked for each respective server call. Once placed into a port for continuing data communication, this server-client port will follow a respective queue (similar to the different types of queues we see in the diagram of slide 9) for processes deriving from similar client request (but coming from different machines/IP's), and return output. These requests are essentially processes requested from client, to become dispatched. The server will provide the correct service per marshalled stub, and will return this output to clients. By following a queue for each ported stub, we can ensure each process gets executed once from server to client, because as soon as we provide output, we move onto the next request/process within the queue. So essentially, we have the main queue at the start of communication which the server's CPU will delegate to a respective port for communication based on packet data. The port will have a queue itself, and a certain quantity of resource allocation, thus sequentially executing client calls by utilizing its given resources per request/process within the port's queue. Once we send data back to client, this request/process is dequeued from the queue and a new one is inserted via main CPU.

When it comes to RPC (Remote Procedure Calls), how would the server handle multiple RPC calls from different clients? Think about process scheduling and how operations are handled on these processes. (Hint: Think about dispatching processes).

For interstellar probes we require an OS that is reliable androbust as it is expected to run correctly for hundreds of years. Keeping the kernel as small as possible would be the better option as it would be more efficient without any significant overhead and easier to debug/maintain. One good choice could be a microkernel, since we can focus on debugging the core system, making sure it has the ability to isolate components, and recover from errors. Other choices mean the system becomes more tightly dependent, which can lead to cascading errors.

Say that you are designing an operating system for an intersellar probe that is meant to run for hundreds of years. What structure (simple, layered, microkernel, or modular) should you choose for its kernel? Explain.

Graphical interface would probably still be the way to go. Vision is an inherently parallel mechanism. To make an analogy: at this moment, you are observing the equivalent of many many pixels. Although your focus centers on only some small subset, you have a general awareness of the entire environment (e.g., your literal or digital desktop). It would be much more difficult to encode a lot of information if we were limited to something like the sense of touch, taste, sound, etc.

The user interfaces discussed in lecture are based on graphics - something that we see and interpret. Assume for a moment that we have output devices capable of stimulating any sense. Would we be better off using a different "sense" if we wanted to optimize presenting lot amounts of information to the user? Explain.

• Stack: This must be kept separate since programs must track their own execution. • Heap: This can safely be combined - all programs are simply linking to allocations at different addresses. • Data: This can safely be combined - variables will get stored in different places. • Text: This can be problematic - code will get stored in different places but we will need some way to distinguish between different entry points (main functions).

There are four general regions to a process in memory: stack, heap, data, and text. List and explain which of these must be separated for each program running and which might be combined globally.

"ABCD", "ADBC", "DABC", "ABDC" (A always prints before B and C, and B always prints before C due to the wait(NULL) function call. D can print anytime.)

Trace the following program and then list all of the possible outputs (just write the A/B/C/D text; no need for pids) that could be generated. (Assume that fork will always succeed.) #include <sys/types.h> #include <stdio.h> #include <unistd.h> int main() { pid_t pid1, pid2; pid1 = fork(); if (pid1 < 0) { fprintf(stderr, "Fork Failed"); return 1; } else if (pid1 == 0) { printf("A"); pid2 = fork(); if (pid2 < 0) { fprintf(stderr, "Fork Failed"); return 1; } else if (pid2 == 0) { printf("B"); } else { wait(NULL); printf("C"); } } else { printf("D"); } return 0; }

The Linux approach would be more secure. Permissions are based on user settings, so files download would not automatically be marked executables. Under Windows, all it takes is download a file with specific name (one ending with .exe) for the file to be an executable.

Under Linux a file is marked as an executable by using a "chmod +x" command to give it execution permissions. Under Windows, a file is given a .exe extension to make it an executable. (Linux executables typically do not have an extension.) Explain and justify which of these two approaches (Linux vs Windows) is more secure for a environment where a user is constantly downloading files online.

a) 16 bytes b) 5 bytes

struct BMP_Header { char signature[2]; int size; char reserved1; enum offset_type offset_pixels; }; a) What is the size of this struct as defined? b) How much space would be wasted with word length padding?

a) The arrow operator should be used instead of the dot operator since my_list is a pointer. b) The memory for the node being removed is not freed.

struct grade_node { int value; char assignment[255]; struct grade_node* next; }; struct grade_node* my_list = ... //assume this has been populated elsewhere. void remove_head() { if (my_list != NULL) my_list = my_list.next; } a) What is the syntax error in remove_node? b) What is the memory issue in remove_node?

a) The head variable is a local variable. The assignment to it (of new_node) will not be visible in the outside scope and the list will remain unchanged. b) There are multiple solutions: void add_last(struct node** head, struct node* new_node) { new_node->next = *head; *head = new_node; }

struct node { int num; struct node* next; }; void add_first(struct node* head, struct node* new_node) { new_node->next = head; head = new_node; } a) This function will fail to give the expected result (i.e., add the node). What is the problem and when does it occur? b) How can it be fixed in C? Give an updated function.

No. The struct cannot be created in memory (or otherwise), since it contains itself. It's the equivalent of a recursive method without a basecase.

struct node; //forward declaration for Node struct node { int num; struct node next; }; Can a program actually create variables that use the Node struct as it is currently defined?

The second function, which uses pointers, should be a little more efficient. In the first function, the execution environment has to make copies of the entire point structures, about 12-bytes each, to make them available as local variables in a stack frame. In the second, only the address (4-bytes) for each of them will need to be copied.

struct point add_points(struct point p1, struct point p2) struct point* add_points(struct point* p1, struct point* p2) Which of these functions should we expect to operate more efficiently and why?

a) The function is returning a pointer to a stack allocation that will be automatically released when it exits. Once the function completes, use of the memory will no longer be valid since another function may be called and reuse that memory. b) The memory for result should be allocated from the heap (via malloc) instead of the stack.

struct point { int x; int y }; struct point* add_points(struct point* p1, struct point* p2) { struct point result; result.x = p1->x + p2->x; result.y = p1->y + p2->y; return &result; } Most of the time function will appear to work fine, but then later there will be an issue with the result point's data changing unexpectly. a) What is the problem? b) How it can be fixed?

a) 12+76+4+4+4 = 100 bytes. b) 3 bytes

struct student { char student_id[10]; char name[75]; float gpa; int credits_earned; int credits_failed; }; a) What is the size of this struct as defined? b) How much space would be wasted with word length padding?

Move declaration before main()

#include <stdio.h> void main() { somefunc(10); } void somefunc(int n) { //prints integers from n to 1. if (n > 0) { printf("%d", n); somefunc(n - 1); } } This program does not compile. What is the issue and how can it be fixed?

void toupper(char* str) { while(*str){ if (*str>96 && *str<123){ *str=*str-32; *str++; } else if (*str==32){ *str++; } else{ *str++; } } }

#include <stdio.h> void toupper(char* str) { //WRITE THIS CODE } void main() { char text[] = "Some sample text"; toupper(text); printf("%s", text);//prints "SOME SAMPLE TEXT" }

p = ma; p = &ma[0][0]; p = &ma; p = ma[0]; p = *ma;

Assume you have declared a multi-dimensional array char ma[5][30]. What is the address of the element "ma[0][0]"?

Pro: It would be efficient in performance due to the assembling of minimized path routines in kernel. Con: Lack of portability due to system specific architecture. Con: Difficult to debug code due to dynamic nature.

An experimental operating system has an assembler incorporated into the kernel. To optimize system-call performance, the kernel assembles routines within kernel space to minimize the path that the system call must take through the kernel. This approach is the antithesis of the layered approach, in which the path through the kernel is extended to make building the operating system easier. Discuss both the pros and cons of this approach to kernel design and system-performance optimization.

An algorithm describes how to go about solving a particular problem while policy depicts the rule has to be followed in order for a goal to be achieved. Although both have their own purpose, it is difficult to replace an algorithm with a policy as then there would be no way to compute (i.e., make logical decision) about "HOW" to achieve something. A policy is a fixed rule to follow. But the vice-versa is (sometimes) possible since algorithms can be used to compute policies. (The only issue is when a policy arbitrary and cannot be computed.)

Can algorithms replace policies? How about vice versa?

Storing buffered file in the PCB would be one approach. It's possible that multiple processes might try to use the same file. Each process could read the file into a local buffer so that it is able to read it without being concerned that another process is writing to it. Likewise a writing process could buffer the modified file and only save it to disk once all the changes are made.

Consider the situation where two processes are reading and writing the same file. Explain how could the PCB concept be extended to avoid concurrent IO issues?

No. The computer with a single core can simulate the multi-core system by using concurrency (e.g., swapping between processes). The multi-core can run anything that the single core can by only assigning processes to one core and disregarding the other three.

Consider two computers, one which has a single physical processor and one which has four physical processors. Are there any algorithmic problems that can only be solved on the system with four processors, or are they equivalent? Explain. (Hint: Speed is not a concern; the speed up of increasing processors is only margin in real world systems.)

function File readFile(File fileOnSystem) { char buffer[BUFFER_SIZE]; while (fgets(buffer, sizeof buffer, stream) != NULL) { // process buffer, then add read bytes to tempFile. tempFile += buffer; } if (feof(stream)) { // hit end of file return tempFile; } } function void saveFile(File fileOnSystem) { char buffer[BUFFER_SIZE]; fwrite(tempFile, sizeof buffer, sizeof tempFile, fileOnSystem); }

Consider using a word processor. If you were writing high-level code for reading and writing a file, what system calls would you expect? Use C-like pseudocode to structure your answer for in terms of two functions: readFile and saveFile. Hint for pseudocode outline: Use a temp file/variable to buffer the contents of a file. In readFile, read a File to the temp file (can use a global variable), so user can read and then edit. When user saves the temp file, it will be written back to the file.

All processes start in the ready queue, and then move to the CPU for computation before moving into another queue that requests resources such as I/O, forking of processes, and listening for interrupts to invoke other actions. Each of these resources are limited and are made available via a queue. Once the resource has been used, the process returns to the ready queue to determine if they can be terminated, or need another round of resource allocation. The ready queue is continuously looping through processes, and feeding it into the CPU to ensure that each process gets resources for as long as it needs while no particular one prevents others from getting CPU/IO time.

Explain why the queue diagram in slide 9 has a continuous cycle that flows between the listed queues and resources? Is a cycle necessary?

The root folder of my computer, a Mac, is a home directory that contains all major sub-directories. For example, bin stores compiled files, local customizations and additions to the standard OS installation, Library directory contains plugin files, language preference files, Application Preferences and binaries, etc. Private for private data such as dhcpd_leases, var for versatile files that are subject to change, tmp for temporary data files. It has has the Users folder for user files sych as Documents, Downloads, Desktop, etc. Basically, Users Folder contains user directory for all personal files that can be edited and viewed through applications from the root directory. Hence why, for installation of applications, I need root permission.

How is the file management hierarchy of your personal computer set up? Explain at least 2 levels of your computer's file management hierarchy. Hint: Think, what is the root folder of your computer? Why is that the root folder? Why are all the other sub-directories setup the way they are, and what files can be found within them?

Ans: [Lisonbee] An application would exist on the highest level on this hierarchy, and the hardware would be on the lowest level. The OS/Kernel sit in-between these two, and effectively form a layer of abstraction between the system calls that an application needs to use, and the low level subroutine that is actually invoked. This layering ensures that applications do not have direct access/control over the hardware (and thus cannot perform operations that may be destructive/malevolent in nature), yet they can still contain functionality to perform lower-level operations. This also ensures that multiple active processes play nice with each other, and allows the OS to manage these different processes and the system calls they make rather than relying on the applications themselves.

How would the hardware/software hierarchy of an OS be layered so that its API can provide system calls from application to OS? Justify your answer

Pseudocode - Example of system calls for rendering a URL in browser and displaying content on screen:Here the browser acts as the client system and needs to establish a connection with the server.The server listens for incoming requests on a particular port. // BROWSER SIDE // socket function creates a unbound socket connection. connectionVariable = socket(domain, type, protocol); // opens a connection port if(connect(connectVariable, *socketaddress, addresslength) == -1) print connection error Loop - continuously listens to and sends the requested data // read data from server data = read(connectionVariable, *buffer, bufferlength, flags); // write to screen write(connectionVariable, *buffer, bufferlength); // SERVER SIDE // socket function creates a unbound socket connection. listenVariable = socket(domain, type, protocol); // Binds the socket created with the socket address if(bind(listenVariable, *socketaddress, addresslength) == -1) print bind error // Server listens for incoming requests on a particular port listen(listenVariable, int backlog); Loop - continously listens to and sends the requested data // Server accepts the connection connectionVariable = accept(listenVariable, *socketaddress, addresslength); // Writes the data onto the connection write(connectionVariable, data buffer, sizeof(data buffer)); // Close the connection close(connectionVariable)

In any computer task, a series of system calls are executed utilizing the kernel's API. Right now, you are reading this document in a web browser - what would be some system calls you imagine for network access and screen display? Use psuedo-code to show what you think the browser's main loop is doing.

ptr is an integer pointer that is equal to the value at address j.

Int* ptr = &j;

ABCD ACBD ACDB CABD CADB CDAB

List all of the possible outputs (just write the A/B/C/D indicators for each printf that will run; no need for pids) that could be generated by the previous program. Again assume that fork will always succeed.

One idea would be to create an enumeration which contains an element for each of the possible pieces of data that the union contains. Then, a variable of the union type can be wrapped with a struct which also contains a variable of the new enum type. The result will be that the union will have "metadata" attached that will indicated how it is being used.

One of the issues with using unions is that it can be unclear which element in the union is the one currently being used. Describe a mechanism to indicate which element is active.

A-0, B-1507, C-1507, D-1505, E-1505, F-1502

Trace the program below, identify the values of the pids at lines A, B, C, D, E, and F. (Assume that the actual pid of Process 1 is 1502, Process 2 is 1505, and Process 3 is 1507. Also assume that fork will always succeed.) #include <sys/types.h> #include <stdio.h> #include <unistd.h> int main() { pid_t pid1, pid2, pid3; pid1 = fork(); if (pid1 < 0) { /* Error occurred */ fprintf(stderr, "Fork Failed"); return 1; } else if (pid1 == 0) { /* Process 2 */ pid2 = fork(); if (pid2 < 0) { /* Error occurred */ fprintf(stderr, "Fork Failed"); return 1; } else if (pid2 == 0) { /* Process 3 */ pid3 = getpid(); printf("pid2 = %d", pid2); /* A */ printf("pid3 = %d", pid3); /* B */ } else { /* Process 2 */ pid3 = getpid(); printf("pid2 = %d", pid2); /* C */ printf("pid3 = %d", pid3); /* D */ wait(NULL); } } else { /* Process 1 */ pid2 = getpid(); printf("pid1 = %d", pid1); /* E */ printf("pid2 = %d", pid2); /* F */ wait(NULL); } return 0; }

An array is a pointer because it needs to indicate the location of the starting element, and the memory addresses of all the proceeding values stored in the data structure. The values are stored together in a linear fashion, so the memory addresses aren't scattered around, and thus our access and manipulation of values can be quick and efficient. What makes it a pointer is that it holds the address of the first element, as mentioned above. Thus, to access elements following elements, we would do the following: say we wanted to access the third element of an array we would use the following method of accessing element: Base memory address + value byte size * (index location). With an address, we couldn't find the other elements in memory.

Why are arrays represented as pointers? Explain.

In Java, arrays are objects and other data can be associated with them. Like the number of elements. In C, arrays are just addresses and don't have any meta-data associated with them. Hence, we have to use a termination character to provide a way to tell how long a string is (i.e. a known stopping point). Else, we only have a starting place and could end up visiting all the memory after it.

Why do we need to use null characters to denote the end of a string? No string termination character is used in Java.

2*6*4 bytes

int multi_array[2][6] = {{10000, 2, 3, 9, 10, 10}, {7, 8, 9, 9, 1999, 0}}; int size = sizeof(multi_array); What is the value of size?

Int x is created P is a pointer to the address of x X is set to 100 P is set to the address value minus 1 Print value of x which was changed by P which is 99.

int x, *p = &x; x = 100; *p = *p - 1; printf("The value of x is %d.\n", x);

Int x is established, int p is the address of x X is set to 100 Error here since p is not a variable and not a pointer so *p must be be used.

int x, *p = &x; x = 100; p = p * 2; printf("The value of x is %d.\n", x);

It's trying to set the first eight bits in the binary representation of j to zero.

int* ptr = &j; *((char*)ptr) = 0; What is this code trying to do to the value stored in j? (Hint: think in terms of bits.)

The string of text indicates the global name of the shared memory. Presumably, some program will look for the shared memory file called "OS", so if it is changed, that program will not be able to read from the shared memory this program creates. b) The string of text in message_1 ("World!") would be written into the shared memory from the very start instead of after the bytes used for "Hello". As are, the shared memory would only contain "World!" instead of "HelloWorld!".

void main() { //code sample from Operating System Concepts, page 132 of Chapter 3 in 9th edition. const int SIZE = 4096; const char *name = "OS"; //LINE A const char *message_0 = "Hello"; const char *message_1 = "World!"; int shm_fd; void *ptr; shm_fd = shm_open(name, O_CREAT | O_RDRW, 0666); ftruncate(shm_fd, SIZE); ptr = mmap(0, SIZE, PROT_WRITE, MAP_SHARED, shm_fd, 0); sprintf(ptr, "%s", message_0); ptr += strlen(message_0); //LINE B sprintf(ptr, "%s", message_1); ptr += strlen(message_1); }

The second. Here we are using a stack allocation. Therefore, on the LINE 1 we know for sure that the memory will be kept alive (the next call is pushed on the stack while this call would stay put). For LINE 2, we are return the address of stack memory which will be returned to the system and used for the next function call even if the memory for build() has been released.

void shift(struct point_2d* p1, int offset) { //...stuff happens... } struct point_2d* build() { struct point_2d data; //...stuff happens... shift(&data, 10) //LINE 1 return &data; //LINE 2 } Of the two lines indicated, which may be more bug prone? Explain in terms of memory and scope. Assume that the shift function is implemented correctly.


Ensembles d'études connexes

biology Biomolecules and Enzymes

View Set

Health and PE: Body Systems Part I-III

View Set

PHYSICS 20E - LIFE IN THE UNIVERSE (midterm 2)

View Set

Identifying Independent and Dependent Variables

View Set