Thanks to visit codestin.com
Credit goes to www.scribd.com

0% found this document useful (0 votes)
128 views19 pages

JAGGU1

Uploaded by

sachinkj143143
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
128 views19 pages

JAGGU1

Uploaded by

sachinkj143143
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 19

Operating System Principles and Programming

“Activity 9 & Activity 10”

Report submitted by

Name: VARUN GANI SRN: 02FE22BCI056


Division: D Roll No.: 53

Under the guidance of

Prof. Gambhir Halse

Prof. Anita Kenchannavar

DEPARTMENT OF
COMPUTER SCIENCE AND ENGINEERING

K. L. E. Dr. M. S. SHESHGIRI
COLLEGE OF ENGINEERING AND TECHNOLOGY,
BELAGAVI– 590008
(2023-24)
ACTIVITY 9
CASE STUDY OF MEMORY MANAGEMENT IN xv6 OPERATING SYSTEM

1. Introduction to xv6 :

The xv6 operating system, developed by MIT, serves as a modern re-implementation


of Unix Version 6 (V6) for educational purposes.
Designed to run on multi-core x86 architectures, xv6 provides a clear and concise
example of a traditional Unix-like operating system. Its compact codebase and well-
structured documentation make xv6 an excellent tool for understanding fundamental
operating system concepts such as file systems, process scheduling, and particularly,
memory management.
Effective memory management is crucial for any operating system as it ensures
efficient use of the computer's memory resources. In xv6, memory management
encompasses several critical tasks, including managing virtual memory, allocating and
deallocating memory, and implementing memory protection mechanisms. Additionally, xv6
illustrates the interaction between hardware and software in memory management,
providing a comprehensive learning experience about how operating systems handle
memory hierarchies, paging, and segmentation. This deep dive into xv6’s memory
management allows learners to appreciate the intricacies and challenges of maintaining a
stable and efficient operating system.

2. Understanding xv6 Memory Management :

A key feature of xv6 is its memory management subsystem, which consists of


multiple components working in unison to allocate, manage, and safeguard memory.
This subsystem is composed of several crucial files, each with a specific role in handling
memory-related operations. Understanding the contents and functions of these files is
essential to grasp how memory is managed in xv6.

Crucial Files for Memory Management in xv6:

 defs.h: This header file contains definitions and function prototypes used throughout
o the kernel. It serves as the central hub for numerous kernel-wide
declarations, ensuring consistency and organization across the kernel
codebase.
 kalloc.c: Responsible for managing physical memory allocation, this file includes
o functions for allocating and freeing memory pages. It is a critical component
for maintaining efficient physical memory usage and ensuring that memory is
available when needed.
 mmu.h: This header file provides definitions for the Memory Management Unit
o (MMU), including macros for virtual address translation and page table
structures. It plays a vital role in enabling the kernel to translate virtual
addresses to physical addresses accurately.
 syscall.c and syscall.h: These files implement and define system calls, bridging the
o gap between user-space applications and kernel functionalities. They handle
the invocation and execution of system calls, ensuring user requests are
properly processed by the kernel.
 user.h and usys.S: These files define user-space system calls and include assembly
o code for invoking them. They provide the interface through which user
applications can interact with the kernel, facilitating the execution of system-
level operations from user space.

Together, these files create a cohesive and efficient memory management approach for
xv6. Each file plays an integral role in the system, contributing to a well-organized and
robust memory management framework.

3. Steps to Implement Copy-on-Write Fork in xv6 :

Implementing a copy-on-write (CoW) fork in xv6 requires modifying the current fork
system call through multiple steps. The CoW mechanism allows the parent and child
processes to share the same memory pages initially, creating copies only when one of
the processes attempts to modify a page. Below is a detailed explanation on how to
achieve this in xv6, with additional points and rephrasing for clarity:

Step 1: Remove Allocation from sbrk()

 The sbrk(n) system function in xv6 increases a process's memory by n bytes.


 In the initial implementation, physical memory pages were allocated immediately.
For CoW, physical allocation must wait until a page is accessed.
 Modify sys_sbrk() in sysproc.c to remove the call to growproc(), which allocates
physical memory.
 Ensure that sbrk(n) only updates the process's size (proc->sz) by n bytes and returns
the previous size without creating memory.

Step 2: Implement Lazy Allocation

 Lazy allocation involves allocating memory pages only when they are accessed,
resulting in page faults.
 Modify trap.c to handle page faults by allocating a new page at the faulting address.
 Use allocuvm() from vm.c to allocate the page and PGROUNDDOWN(a) to align the
faulting address with a page boundary.
 Update the page table entries and return from the trap handler to continue process
execution.

Step 3: Monitor Free Pages

 Implement a system call getNumFreePages() to return the total number of free


pages in the system, aiding in debugging and verifying the CoW mechanism.
 Modify kalloc.c to maintain a count of free pages.
 Use the getNumFreePages() function to retrieve this count.

Step 4: Monitor Both Virtual and Physical Pages

 Implement system functions getNumVirtPages() and getNumPhysPages() to


determine the number of virtual and physical pages in a process's memory.
 getNumVirtPages(): Count the number of logical pages from virtual address 0 to the
process's virtual address space size.
 getNumPhysPages(): Examine the page table and count the number of valid entries
for physical pages.

Step 5: Track Page Table Pages

 Create a system call getNumPTPages() to determine the number of pages used by a


process's page tables.
 This count should include the page directory and all pages used for inner page tables.

Step 6: Adjust fork() to Implement CoW

 The primary changes for CoW occur in the copyuvm function in vm.c:
 Do not duplicate the parent’s pages when forking. Instead, update the page tables to
point to the same physical pages, allowing the parent and child to share them.
 Mark these shared pages as read-only to trigger page faults on write attempts.
 Modify the reference counts for shared pages in kalloc.c to keep track of how many
processes share each page.

Step 7: Manage CoW Page Faults

 Within the trap.c page fault handler:


 Identify write attempts to shared pages (they will be read-only).
 Copy the contents of the original page into a new page.
 Adjust reference counts and update the page table to point to the new page.
 Use lcr3(v2p(pgdir)) when modifying page table entries to ensure correct TLB
flushes.

Step 8: Test the Implementation

 Create test programs to verify the correctness of the CoW fork implementation:
 Use getNumFreePages() to monitor free pages and observe changes during various
operations.
 Write programs that perform read/write operations and fork processes to ensure
CoW behavior is triggered and handled correctly.
 Ensure the system behaves as expected, with memory being copied only upon write
attempts.

By following these detailed steps and implementing the described modifications, the
CoW fork mechanism can be successfully integrated into xv6, enhancing its memory
management efficiency and performance.

4. Implementation of Lazy Memory Allocation :

Lazy memory allocation is a technique that delays the allocation of physical memory
pages until they are actually accessed, which can enhance performance and conserve
memory resources. Here is a detailed guide on how to implement lazy memory
allocation in xv6, with additional points and rephrasing for clarity:

Step 1: Adjust sbrk() to Support Lazy Allocation

 In the conventional xv6 implementation, the sbrk() system call allocates the required
physical memory and increases the process's memory capacity by n bytes. To
implement lazy allocation:
 Modify the sys_sbrk() function in sysproc.c:
 Remove or comment out the growproc() call.
 Ensure that sbrk(n) does not allocate physical memory; instead, it only increases
proc->sz by n bytes and returns the previous size.
 Example implementation :

int sys_sbrk(void) {

int addr;

int n;

if(argint(0, &n) < 0) {

return -1;

addr = proc->sz;

proc->sz += n;

return addr;
}

Step 2: Manage Page Faults for Lazy Allocation

 A page fault occurs when a process attempts to access a page that has not been
allocated. At this point, the trap handler must allocate the page.
 Modify trap.c to handle page faults:
 Check if tf->trapno == T_PGFLT to determine if the fault is a page fault.
 Use the rcr2() method to obtain the faulting virtual address.
 Use PGROUNDDOWN(a) to align the faulting address with the page boundary.
 Example implementation:

void trap(struct trapframe *tf) {

if(tf->trapno == T_PGFLT) {

uint a = rcr2();

a = PGROUNDDOWN(a);

if(allocuvm(proc->pgdir, a, a + PGSIZE) == 0) {

cprintf("Lazy allocation failed\n");

proc->killed = 1;

return;

...

Step 3: On-Demand Memory Allocation

 Allocate the required page and update the page table in the event of a page fault.
 Use allocuvm() to allocate the physical page.
 Ensure that the page table entry for the faulting address is updated to point to the
newly allocated page.
 Example implementation :

if(allocuvm(proc->pgdir, a, a + PGSIZE) == 0) {

cprintf("Lazy allocation failed\n");

proc->killed = 1;
}

Step 4: Examining Lazy Allocation

 Create test programs to verify that lazy memory allocation is being utilized:
 Use sbrk() to increase the process memory capacity without accessing it
immediately.
 Ensure that accessing the newly allocated memory space triggers page faults,
causing physical pages to be allocated.
 Monitor the behavior to confirm that memory is allocated on demand.
 Test Program Example :

#include "types.h"

#include "stat.h"

#include "user.h"

int main(void) {

char *mem;

int i;

mem = sbrk(4096 * 10); // Request 10 pages

for(i = 0; i < 4096 * 10; i += 4096) {

mem[i] = i; // Access each page to trigger allocation

printf(1, "Accessed page at address: %p\n", &mem[i]);

exit();

Step 5: Additional Testing and Optimization

 Conduct extensive testing to ensure the robustness of the lazy allocation mechanism
under various conditions and workloads.
 Optimize the trap handler to minimize overhead and ensure efficient handling of
page faults.
 Monitor the overall system performance and memory usage to verify the benefits of
lazy allocation.
By following these steps and implementing the described modifications, lazy memory
allocation can be effectively integrated into xv6.

5. Implementation of Copy-on-Write Fork :

The copy-on-write (CoW) fork mechanism in xv6 allows the parent and child
processes to initially share the same memory pages, creating copies only when one of
them tries to modify the shared pages. This method enhances performance and
optimizes memory utilization. Here’s a detailed guide on how to implement the CoW fork
in xv6, with additional points and rephrasing for clarity:

Step 1: Use kalloc.c to Track Reference Counts

 To manage shared pages, you must track how many processes reference each page.
 Add a reference count array to kalloc.c to monitor the number of references to each
physical page.
 Update the reference count upon allocation or release of a page.
 Example implementation

#define MAX_PAGES (PHYSTOP / PGSIZE)


int ref_count[MAX_PAGES];
void inc_ref(uint pa) {
acquire(&kmem.lock);
ref_count[pa / PGSIZE]++;
release(&kmem.lock);
}

void dec_ref(uint pa) {


acquire(&kmem.lock);
if(--ref_count[pa / PGSIZE] == 0){

kfree((char*)pa);
}
release(&kmem.lock);
}

Step 2: Adjust proc.c and vm.c's fork() Function

 Modify the copyuvm function in vm.c to share pages between the parent and child
instead of copying them.
 Set shared pages to read-only to detect modification attempts.
 Example implementation :

pde_t* copyuvm(pde_t *pgdir, uint sz) {


pde_t *d;

pte_t *pte;

uint i, pa, flags;

char *mem;

if((d = setupkvm()) == 0)

return 0;

for(i = 0; i < sz; i += PGSIZE) {

if((pte = walkpgdir(pgdir, (void *)i, 0)) == 0)

panic("copyuvm: pte should exist");

if(!(*pte & PTE_P))

panic("copyuvm: page not present");

pa = PTE_ADDR(*pte);

flags = PTE_FLAGS(*pte);

*pte = pa | flags | PTE_COW;

if(mappages(d, (void*)i, PGSIZE, pa, flags) < 0)

goto bad;

inc_ref(pa);

return d;

bad:

freevm(d);

return 0;

}
Step 3: Address Page Faults in CoW

 Modify the page fault handler in trap.c to handle writes to CoW pages by copying the
page and updating the page table entry.
 Detect if a page fault is due to a CoW page.
 Allocate a new page, copy the data from the old page, and update the page table.
 Adjust reference counts accordingly.
 Example implementation

void pagefault_handler() {

uint va = rcr2();

pte_t *pte = walkpgdir(proc->pgdir, (void *)va, 0);

uint pa = PTE_ADDR(*pte);

char *mem;

if(!(*pte & PTE_COW))

panic("pagefault_handler: not a COW page");

if((mem = kalloc()) == 0) {

cprintf("pagefault_handler: out of memory\n");

proc->killed = 1;

return;

memmove(mem, (char*)P2V(pa), PGSIZE);

*pte = V2P(mem) | PTE_P | PTE_W | PTE_U;

lcr3(V2P(proc->pgdir));

dec_ref(pa);

Step 4: Set Read-Only status for pages

 To cause page faults when someone tries to write on a shared page, make sure the
page is set as read-only.
 Example:
for(i = 0; i < sz; i += PGSIZE){
pte = walkpgdir(pgdir, (void *) i, 0);
*pte &= ~PTE_W;
}

Step 5: Verifying the Implementation of CoW

 Make test programs that: in order to verify the CoW fork implementation
 Processes should be branched out and made to start sharing pages.
 To initiate CoW behavior and confirm that new pages are allocated appropriately,
write to shared pages.
 Make sure reference counts are updated correctly by keeping an eye on them.
 Test Program Example:

#include "types.h"
#include "stat.h"
#include "user.h"
int main(void)
{
int pid;
char *mem = sbrk(4096); // Request one page
mem[0] = 'x'; // Access to trigger page allocation
If((pid = fork()) == 0) {
// Child process
printf(1, "Child: mem[0] = %c\n", mem[0]); // Should print 'x'
mem[0] = 'y'; // Write to trigger CoW
printf(1, "Child: mem[0] = %c\n", mem[0]); // Should print 'y'
} else {
// Parent process
wait();
printf(1, "Parent: mem[0] = %c\n", mem[0]); // Should print 'x'
}
exit();
}

In order to confirm that the CoW behavior is proper, this software forks a process,
gains access to shared memory, and then starts writing to the shared memory.

6. Testing and Debugging the CoW Fork Implementation :

Comprehensive testing and debugging are necessary to guarantee the accuracy and
effectiveness of the Copy-on-Write (CoW) fork solution. Here's how to test and debug
the CoW fork step-by-step:
Step 1: Functionality Assessment : Create a basic test program to verify that the CoW
fork functions as intended. The test should confirm that changes cause the allocation of new
pages and that the parent and child processes first share pages.

Step 2: Examine Stress : To stress test the CoW implementation, create a test that
repeatedly forks and writes to shared memory.

Step 3: Keep an eye on free pages : To make sure that memory is allocated and released
appropriately, change the test programs to print the amount of free pages before and after
forks.

Step 4: Troubleshooting : If problems arise, apply the following debugging methods:


To log important events like page faults, memory allocations, and reference count updates,
include cprintf statements in the CoW implementation. Step through the CoW fork code
using gdb to examine the memory and page table states.

Conclusion:

Participants in the case study on memory management in xv6 OS got a practical


introduction to process management and memory allocation methodologies. Through the
examination of essential elements within the xv6 source code and the integration of
functionalities such as lazy memory allocation and copy-on-write fork, the attendees
acquired pragmatic understanding of contemporary operating system design principles.
Participants gained important memory management skills and information from this lesson,
which improved their comprehension of programming techniques and operating system
concepts.
ACTIVITY 10
Adhering to Professional Coding Standard

1. Introduction :
The pwgen application, commonly used to generate random passwords, is available
from SourceForge. This report aims to evaluate the source code of pwgen for its adherence
to the ISO/IEC 9899:2018 (C17) standard, which specifies the requirements for the C
programming language. The C17 standard, an update to the previous C11 standard,
introduces clarifications and modifications that enhance the language's robustness.
Adhering to the C17 standard ensures that C code is consistent, reliable, and portable
across different platforms and compilers. This evaluation focuses on key aspects of the
pwgen source code, such as adherence to coding best practices, effective error handling,
correct syntax and semantics, and the proper use of standard library functions.
By thoroughly assessing the pwgen source code against these criteria, the report
aims to identify areas of strength and opportunities for improvement, ultimately
contributing to the development of high-quality, standards-compliant C applications.

2. Overview of ISO/IEC 9899:2018 (C17) Standard :

The ISO/IEC 9899:2018 (C17) standard encompasses a comprehensive set of


specifications for the C programming language, ensuring that C programs are written and
executed consistently and reliably across different platforms. The key characteristics of this
standard include:

 Syntax and Semantics: Defines the rules for constructing valid C programs,
o including details on data types, control structures, expressions, and
declarations. It ensures that the code follows a coherent structure and
adheres to expected behaviors.
 Standard Library Functions: Specifies a collection of standard library functions
o essential for various operations such as input/output handling, memory
management, string and text manipulation, and mathematical computations.
These functions provide a robust foundation for developing complex
applications.
 Error Handling: Provides guidelines for error detection and management, including
o the use of standard error handling mechanisms like errno. This ensures that
programs can gracefully handle unexpected situations and maintain stability.
 Portability: Emphasizes the importance of writing code that can be compiled and
o executed on different systems and compilers without modification. This
promotes the development of versatile and adaptable software solutions.
 Best Practices: Encourages the adoption of maintainable code structures, thorough
o documentation, and a consistent coding style. This includes
recommendations for clear and concise code, proper commenting, and
adherence to naming conventions.

 Security: Highlights the importance of writing secure code to prevent vulnerabilities.


o This includes avoiding common pitfalls such as buffer overflows, using safe
functions, and performing proper input validation.
 Performance: Suggests techniques for optimizing the performance of C programs,
o such as efficient use of resources, minimizing overhead, and leveraging
compiler optimizations.
 Debugging and Testing: Recommends practices for effective debugging and
o testing, including the use of assertions, unit tests, and debugging tools. This
ensures that the code is thoroughly tested and any issues are promptly
identified and resolved.

By adhering to these specifications, developers can create C programs that are robust,
efficient, and maintainable, ultimately leading to high-quality software that meets industry
standards.

3. Analysis of pwgen Source Code :

The pwgen source code was downloaded and reviewed to assess its compliance with
the C17 standard. Key aspects of the code were examined, including syntax and semantics,
use of standard library functions, error handling, and portability.

3.1. Syntax and Semantics

The source code of pwgen adheres to the essential syntax and semantics defined
within the C17 standard. The code is written clearly and concisely, following the typical
structure of C programs with appropriate use of data types, control structures, and
functions. The adherence to proper coding conventions enhances readability and
maintainability.

Example:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <time.h>

/* Function to generate a random password */


void generate_password(int length) {

static const char charset[] =


"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

char password[length + 1];

for (int i = 0; i < length; i++) {

int key = rand() % (int)(sizeof(charset) - 1);

password[i] = charset[key];

password[length] = '\0';

printf("%s\n", password);

int main(int argc, char *argv[]) {

srand(time(NULL));

int length = 8; // Default password length

if (argc > 1) {

length = atoi(argv[1]);

generate_password(length);

return 0;

3.2. Use of Standard Library Functions

The pwgen code appropriately uses standard library functions for input/output
operations (printf, scanf), string manipulation (strlen, strcpy), and random number
generation (rand, srand). This utilization aligns with the C17 standard library specifications,
ensuring that the program benefits from the reliability and portability of standard functions.

Example:

#include <stdio.h>

#include <stdlib.h>

int main() {

printf("Enter a number: ");

int num;

scanf("%d", &num);

printf("You entered: %d\n", num);

return 0;

3.3. Error Handling

The pwgen code includes basic error handling, such as checking the validity of user
input and handling memory allocation failures. However, the error handling can be
improved by using the errno mechanism defined in the C17 standard to provide more
informative error messages and better handle exceptional conditions.

Example:

#include <errno.h>

void generate_password(int length) {

char *password = malloc(length + 1);

if (password == NULL) {

perror("Failed to allocate memory");

exit(EXIT_FAILURE);

}
// ... (password generation logic)

free(password);

3.4. Portability

The pwgen code is generally portable across different platforms and compilers, as it
relies on standard C functions and avoids platform-specific features. The use of #include
directives for standard headers ensures compatibility with the C17 standard, making the
code versatile and adaptable for various environments.

Example:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <time.h>

4. Suggestions :

To further improve compliance with the C17 standard, the following enhancements
are recommended:

 Enhanced Error Handling:


o Incorporate the use of errno and perror for more comprehensive error
handling. This will provide clearer diagnostics and aid in debugging.
o Implement error handling mechanisms for all critical operations, such as file
operations and memory allocations.
 Code Comments and Documentation:
o Add detailed comments and documentation to improve code readability and
maintainability. Each function should have a header comment explaining its
purpose, parameters, return values, and potential side effects.
o Use inline comments to explain complex or non-obvious code sections.
 Consistent Formatting:
o Ensure consistent code formatting and style throughout the codebase. Adopt
a style guide and use tools like clang-format to automate formatting.
o Maintain consistent naming conventions for variables and functions to
enhance readability.
 Modularization:
o Refactor the code to use a more modular design. Break down large functions
into smaller, reusable functions to improve readability and maintainability.
o Use header files to declare functions and types, ensuring a clear separation
between interface and implementation.
 Security Considerations:
o Replace the use of rand() with a more secure random number generator,
such as random(), or better, a cryptographically secure RNG if available.
o Ensure that passwords are generated with sufficient entropy and randomness
to meet security best practices.
 Testing and Validation:
o Develop a comprehensive suite of unit tests to verify the functionality of each
component. Use frameworks like CUnit or Unity for C.
o Implement continuous integration to automatically run tests on each commit,
ensuring ongoing code quality.

5. Conclusion :

The pwgen source code adheres to the basic aspects of the ISO/IEC 9899:2018 (C17)
standard, including syntax, semantics, and the use of standard library functions. However,
there are areas for improvement, particularly in error handling, code documentation, and
formatting consistency. By addressing these suggestions, the pwgen code can achieve
greater compliance with the C17 standard, enhance its overall quality, and ensure better
maintainability and portability.

Additionally, incorporating best practices in security, modularization, and automated


testing will further solidify the robustness and reliability of the application. These
improvements will not only make the code more readable and maintainable but also ensure
that it performs reliably across different environments and use cases.

You might also like