User Tools

Site Tools


opus:fall2011:bh011695:start

Table of Contents

Brad Hammond's Fall 2011 Opus

LDA #$FF

Introduction

Welcome to Opus Land. Here you will find all kinds of opusy like things directly related to my Opus. See the opus. Hear the opus. Touch the opus. Become one with the opus. However, there are certain things one should never ever ever do with their opus. This will be a compendium of such things. Some of these things may seem like common sense. However, others will not.

  • Wear your opus like a hat
  • Set fire to your opus
  • Slam your opus in your text book
  • Jump on your opus
  • Slap someone with your opus
  • Let WezlBot read your opus It will give him the means to peer into your soul.
  • Present your opus in the form of a singing telegram Just because your voice sounds good in the shower, doesn't mean we'll enjoy it.
  • Fold your opus into a paper airplane
  • Give your opus to Jansen as you'll be scarred for life.
  • Fly your opus like a kite
  • Write your opus in binary. It'll take you long enough to write this thing. You'll be here for years working on the first part.
  • Most importantly, don't ever read your opus out loud in an old cabin in the middle of the woods. Candarian demons will start to inhabit the souls of your friends and it'll just turn into a crappy weekend from there. Trust me.

Part 1

Entries

September 19, 2011

Doubly Linked List Class

Well, I think I'm finally at a good point with my linked list class. I may add a few more functions that, perhaps, compare linked lists to see if they're the same or copy one linked list into another linked list. As it stands right now many of the functions are overloaded to handle multiple data types. I can append and insert nodes into the list as well as delete nodes. Creating this class and implementing it really boosted my knowledge on doubly linked lists. At first, I was like, “huh?” but now I feel I've reached a decent level of understanding. Doing the insert functions was where I really had to think about things. Dropping a new node and relinking all the pointers to the right places really hurts the brain at first. I'd also like to try an implementation with templates. This will minimize the number of functions and, hopefully, will really cement my understanding of the subject.

September 23, 2011

Function Pointer

I think I've come to understand the whole function pointer thing. It was just hard to come up with a practical use for them. In, C++ I remember discussing that the only difference between a class and a struct in C++ was that a class contains private members whereas in a function everything is public. However, in C structs cannot contain any functions. However, it is possible to create a function and put a pointer function within the struct. Now you can have a semi-sort of member function for your struct.

October 1, 2011

We've moved onto creating a stack today. Well, I guess I should say yesterday at this point but… At first it was kind of weird. I thought it would be difficult to mold the idea into my Linked List Class but it really didn't take that much. Instead of creating several Node pointers, I just instantiated a linked list object called stack. To push to the stack, I would just call the insert function and insert to the first point in the list. When popping from the stack I would just get the value at the first point in the list and then delete that node. Right now, I'm trying to get a palindrome checker using the stack but it's being a little goofy.

Month Day, Year

This is a sample format for a dated entry. Please substitute the actual date for “Month Day, Year”, and duplicate the level 4 heading to make additional entries.

As an aid, feel free to use the following questions to help you generate content for your entries:

  • What action or concept of significance, as related to the course, did you experience on this date?
  • Why was this significant?
  • What concepts are you dealing with that may not make perfect sense?
  • What challenges are you facing with respect to the course?

Remember that 4 is just the minimum number of entries. Feel free to have more.

DATA Topics

Pointer

A pointer is a data type that contains the memory address where another value is stored in memory.

#include <stdio.h> 
 
int main()
{
	int regVar = 0;
	int *pointVar = &regVar;
 
	printf("regVar is stored at 0x%x. \n", &regVar);
	printf("pointVar points to 0x%x. \n", pointVar);
	printf("regVar = %d\n", regVar);
	printf("The value of pointVar = %d\n", *pointVar);
 
	return 0;
}
lab46:~$ ./pointer
regVar is stored at 0x22ff74.
pointVar points to 0x22ff74.
regVar = 0
The value of pointVar = 0
lab46:~$

Array

An array is a sequence of equally spaced address in memory. Arrays usually come in two flavors: one dimensional arrays and two dimensional arrays. One dimensional arrays are in a row format. Ex. array[0], array[1], array[3]… Two dimensional arrays use a row column format. Ex. array[0][0], array[0][1], array[0][2]…

Pointer Arithmetic

Pointer arithmetic is a means of manipulating a position in memory via addition or subtraction. Pointer arithmetic and array subscripting are two ways of doing the same thing. To do the same

#include <cstdlib>
#include <iostream>
 
using namespace std;
 
int main()
{
    int *intPointer, x;
    intPointer = (int*) malloc(sizeof(int)*5);
 
    for (x = 0; x < 5; x++) cout << intPointer++ << endl;
 
    return 0;
}
lab46:~$ ./pointerArithmetic
0x2145010
0x2145014
0x2145018
0x214501c
0x2145020
lab46:~$

Null Pointer

In C/C++, a null pointer is a special case. Null is a mnemonic which makes it a little clearer that there is no object associated with a pointer. Integers and memory addresses are interchangeable. Zero, null, is the exception because in C it is guaranteed that 0 will never be a valid memory location. Attempting to access a null pointer would be bad. If you're fuzzy on the whole good bad thing, I want you to imagine all life as you know it stopping instantaneously, and every molecule in your body exploding at the speed of light.

#include <stdio.h>
 
int main()
{
    char *cp = NULL;
    printf("%c\n", *cp);
 
    return 0;
}
lab46:~/src$ ./nullPoint
Segmentation fault (code dumped)
lab46:~/src$ 

Void Pointer

A void pointer can be looked at kind of like a shape shifter. It can store an address to any data type. In C, it will be converted to any other data type when assigned. However, it can't be dereferenced without a cast.

#include <stdio.h>
 
int main()
{
	int i = 0;
	char c = 'A';
 
	void* q = &i;
	printf("%d\n", *(int*)q);
 
	q = &c;
	printf("%c\n", *(char*)q);
 
	return 0;
}
lab46:~$ ./voidPoint
0
A
lab46:~$

Memory Allocation: Static Vs Dynamic

Static memory allocation is the process of the allocating memory at compile time. This happens when variables are declared locally within a function.

{
   int i, j;
 
   i = 1;
   j = i + 4;
}

With dynamic memory allocation, memory is created at run time. In C, malloc() is used to allocate memory and in C++ the keyword new is used. This memory will exist until the user deallocates it via free() in C, and delete in C++ or the garbage collector takes care of it. In C

{ 
	int *i = NULL;
 
	i = malloc(sizeof(int));
 
	free(i);
 
	return 0;
}

In C++

{
	int *i;
 
	i = new int;
 
	delete i;
 
	return 0;
}

Structures (structs)

A structure, or struct, is a compound data type consisting of multiple variables of, possibly, different data types. This makes for a nice way to group information that is related. This can be particularly useful when accessing records within a file. For instance, you may have a file which contains the first and last name and age of the employees at a company.

struct empInfo {
   char firstName[50];
   char lastName[50];
   int age;
};

If we want to access the members of a struct, we'd do as follows.

   struct empInfo joe;
   joe.firstName = "Joe";
   joe.lastName = "Smith";
   joe.age = 29;

Structure Pointer

A structure pointer is a much like any other pointer one would use. Using the structure example above, to create a pointer to that structure, one would do as follows:

   struct empInfo *empPoint;
   empPoint = malloc(sizeof(struct empInfo));

Accessing the members of a struct pointer is a little different than accessing the members of a struct declared statically. Lets say we want to store the age of an employee into our struct, we'd do the following:

   empPoint -> age = 20;

Linked List

A linked list is an unspecified number of structure pointers all chained together. Only the start and the end are specified. Every other part of the chain is linked by the “next” pointer in the previous part of the chain. In order to maneuver through the list, one needs to resume back to the beginning of the list and move forward.

struct Node {
   int value;
   struct Node *next;
};

The above struct is the core of a linked list. The variable value stores what integer data we have. The node pointer next points to the next item in the list. If there is only one item in the list or this node is the end of the list, next points to NULL. If we're somewhere in the middle of the list, next will point to whatever the next item in the list is.

int value, i;
 
if (pos > listLength || pos < 0) 
	pos = 0;
 
tmp = start;	
 
for (i = 0; i < pos; i++)
	tmp = tmp -> next;	
 
value = tmp -> intVal; 

This code first checks if the desired position in the list is out of bounds and sets the pos to 0. We then move to the beginning of the list and cycle through until we reach the desired position and retrieve the value

Doubly Linked List

A doubly linked list isn't much different that a singly linked list. The only major difference is that your struct will also contain a pointer to the previous item in the list. With this type of list you can move from either the beginning of the list to the end or from the end to the beginning.

struct Node {
  int value;
  struct Node* prev;
  struct Node* next;
};

The only difference between this struct and the one from above is the Node pointer prev. This means that there will be a few more loose ends to tie up. So, if we were to delete a node somewhere in the middle of the list.

tmp -> next -> prev = tmp -> prev;
tmp -> prev -> next = tmp -> next;
tmp -> prev = NULL;
tmp -> next = NULL;
free(tmp);

In the above code, first, we set the prev pointer in the next list element to point at the list element before tmp. So, if we have 3 nodes One, Two, and Three, and we're deleting node Two, we're Three's prev to point at Two. Now we set the next pointer of the previous element in the list to point at the node after tmp. So, One's next will point at Three. We then set all of the pointers within Two to NULL and deallocate the memory.

Function Pointers

A function pointer is a bit of a special case pointer. A function starts at a certain address in memory. We can dereference a pointer to invoke a function.

#include <stdio.h>
 
int add(int, int);
int sub(int, int);
 
struct Thing {
	int val1, val2;
	int (*addition)(int, int);
	int (*subtraction)(int, int);
};
 
int main()
{
	int x, y;
	struct Thing myThing;
 
	myThing.addition = &add;
	myThing.subtraction = &sub;
 
	printf("First int: ");
	scanf("%d", &x);
 
	printf("Second int: ");
	scanf("%d", &y);
 
	printf("Addition: %d\n", myThing.addition(x, y));
	printf("Subtraction: %d\n", myThing.subtraction(x, y));
 
	return 0;
}
 
int add(int a, int b)
{
	return a + b;
}
 
int sub(int a, int b)
{
	return a - b;
}

In C, it's not possible to declare a function inside of a struct. However, we can create function pointers to work around and have some functions within our struct to process the struct's data.

Version Control

Version control, in relation to our coursework, is the monitoring and manipulation of the changes within our source code. We can create changes and upload them to our repository, revert to an older change within the repository, and also we can view any changes that have been made.

lab46:~/src/cpu$ svn update
At revision 432.
lab46:~/src/cpu$
lab46:~/src/cpu$ 
------------------------------------------------------------------------
r3 | bh011695 | 2011-01-27 13:05:23 -0500 (Thu, 27 Jan 2011) | 1 line

added nandGate function
------------------------------------------------------------------------
r2 | jr018429 | 2011-01-27 09:56:47 -0500 (Thu, 27 Jan 2011) | 1 line

added boolean operators
------------------------------------------------------------------------
r1 | wedge | 2011-01-23 19:30:06 -0500 (Sun, 23 Jan 2011) | 1 line

Initial commit
------------------------------------------------------------------------

SYSPROG Topics

User Space

In Unix memory is segregated into two sections. First there is kernel space and secondly there is also user space. User space encompasses the area where the memory is used for user applications.

Kernel Space

The location in memory where the operating system is stored is referred to as system space or kernel space. The operating system is referred to as the kernel. The kernel has sole access to any kind of peripheral device such as a printer, mouse, or keyboard. If an application wants to access any of these devices, it requires permission from the kernel.

File Access (open, read, write, lseek, close)

open() read/write flags

The function open of type int which returns a file descriptor. If opening the file was unsuccessful a -1 will be returned, otherwise will return an integer. It takes, as parameters a filename and an int constant, the read/write flag, to tell the function what you plan on doing with the file: reading, writing, or reading and writing.

read()

read() is a system call which reads from a file descriptor. As parameters, it takes a file descriptor, a void pointer buf, and a count. read() will read count number of bytes starting from buf into the buffer from the file descriptor.

write()

write() is used to store data into a file. write() has the parameters: a file descriptor, buffer, and amount. write() will take amount bites from buffer and send them to the file descriptor. If successful, write() will return the number of bytes written. If it fails it will return a -1. It is possible for the number of bytes sent to be different from the number of bytes requested. The system could possibly put a limit on the size of a file. There is also the possibility of disk space. Perhaps there is no more space left on the hard drive.

lseek()

lseek() is used to find an offset within a file. It takes, as parameters, a file descriptor, the distance in bytes from the base, and the base which can be 0: start of file, 1: current position within the file, or 2: the end of the file.

close()

Whenever finished with a file descriptor, it needs to be closed. close() takes as a parameter a file descriptor and returns a 0 upon success and a -1 on error. An error, for example, if you were to try to open a file that does not exist a -1 will be returned by close().

#include <stdio.h>
#include <stdlib.h>
 
#define OFFSET 100
#define BASE 0
#define BUFF_SIZE 50
 
int main(int argc, char** argv)
{
   char *filePath, c[BUFF_SIZE];
   int fd1, fd2;
 
   filePath = (char*) malloc(sizeof(char) * 100);
 
   if (argc != 2) {
     printf("error: no filepath entered\n");
   } else {
     fd1 = open(argv[1], "r");
     fd2 = creat("myFile", 644);
     lseek(fd1, OFFSET, BASE);
     read(fd1, c, BUFF_SIZE);
     write(fd2, c, BUFF_SIZE);
     close(fd1);
     close(fd2);
   }
 
   return 0;
}

The data outputted to the file is a tad unintelligible. I thought I was doing everything right. It would appear that things aren't getting read into the buffer properly but I'm not sure why that would be.

File descriptors

A file descriptor is a means for which a process can talk to a file. When you open a file, a file descriptor will be created. A -1 signifies that there was an error. However, if there was no error an integer will be returned that will. This is the file descriptor. Any files opened will have a different file descriptor. This file descriptor will be used for operations performed on the file.

fd = open("myFile", "r");

This is how one would create a file descriptor.

Buffering

Buffering is the means of taking data and storing in memory in chunks of a predetermined size. An example of this would be prompting the user for some character data and storing the first 50 bytes into an array. Once the desired operations have been performed on the data another 50 bytes would be stored into the array. Repeat this until there is no more data to process. The kernel has its own buffers which it uses to save time. It keeps copies of disk blocks in memory because of how long it would take to keep writing back and forth between memory and the hard disk.

System Calls

A system call is a function that requests a service from the kernel. System calls can be divided into several groups: process control, file management, device management, information maintenance and communication. read() write() open() and close are all examples of a system call.

File Types and File Properties

When you do an ls -l a lot of information is displayed. However, on the far left you can find the file type designation. There are many different types of files such as directories, regular files, sockets, symbolic links, or named pipes. In one of the earlier examples we used creat() to create a file. When creat() is called a filetype is designated to to the file. It is simply just a regular file. However, different system calls can be used to create different types of files.

A file also has many different properties such as its owner, its permissions, its name, the last time it had been written to, its size, or its create data. Like the file type, all of these things can be displayed by doing a simple ls -l. Using chown(), it is possible to be able to change the owner of a file. As parameters, chown() takes the path to the file, the owner id, and the group id. It is also possible to change the permission bits of a file using chmod(). It's parameters are a file path and the new permissions.

bh011695@lab46:~/discrete$ ls -l
total 776
-rw-r--r-- 1 bh011695 lab46     96 Sep 17 13:05 Makefile
-rwxr-xr-x 1 bh011695 lab46 684685 Sep 23 23:35 a.out
-rwxr-xr-x 1 bh011695 lab46   7104 Sep  7 15:45 assignmentFive
-rw-r--r-- 1 bh011695 lab46    654 Sep  9 17:34 assignmentFive.c
-rwxr-xr-x 1 bh011695 lab46   7065 Sep 10 10:07 assignmentOne
-rw-r--r-- 1 bh011695 lab46    602 Sep  9 17:36 assignmentOne.c
-rwxr-xr-x 1 bh011695 lab46   8553 Sep  9 15:38 assignmentTwo
-rw-r--r-- 1 bh011695 lab46    776 Sep  9 17:33 assignmentTwo.c
-rwxr-xr-x 1 bh011695 lab46   8046 Sep 17 13:09 count
-rw-r--r-- 1 bh011695 lab46    344 Sep 17 13:09 count.c
-rw-r--r-- 1 bh011695 lab46   1872 Sep 17 13:09 count.o
-rw-r--r-- 1 bh011695 lab46   4154 Sep 23 23:34 libStringLib.a
drwxr-xr-x 2 bh011695 lab46   4096 Sep 29 10:21 stringLib
-rw-r--r-- 1 bh011695 lab46    874 Sep 17 12:30 stringLib.c
-rw-r--r-- 1 bh011695 lab46    228 Sep 17 12:30 stringLib.h
-rw-r--r-- 1 bh011695 lab46   2312 Sep 23 23:39 stringLib.o
-rwxr-xr-x 1 bh011695 lab46   7697 Sep 23 23:40 test
-rw-r--r-- 1 bh011695 lab46   3237 Sep 24 14:31 test.c
-rw-r--r-- 1 bh011695 lab46   1552 Sep 23 23:39 test.o
-rw-r--r-- 1 bh011695 lab46    120 Sep 23 23:30 test1.c

Bit Sets and Bit Masks

Bit masking involves checking to see if a certain bit is a one or a zero. To turn a bit on a logical or operation is used. To turn a bit off a logical and is used. It also possible to check the status of a single bit or to change multiple bit values.

#include <stdio.h>
 
int main()
{
  int num = 97;
  int mask = 128;
 
  printf("num = %d\n", num);
  //Turn on the left most bit
  printf("num = %d\n", num = num | mask);
  //turn off the left most bit
  mask = 127;
  printf("num = %d\n", num = num & mask);
 
  return 0;
 }
lab46:~$./mask
num = 97
num = 225
num = 97

User IDs, Group IDs

An unsigned integer value is designated to every user within the system. This UID ranges between 0 and 32767. 0 is always designated to root. A GID, or group ID, follows the same convention. Any user on the system can run the id command to obtain their user and group ID.

bh011695@lab46:~$ id
uid=5577(bh011695) gid=5000(lab46) groups=5000(lab46),1734(sys),1738(data),2999(bigworld),5577(bh011695)

Filesystem Structure; inodes and data blocks

An inode stores information about a file The only things the inode does not contain are the files data and the file's name. Every file on the system has an inode associated with it. All of the inodes on the system are contained inside of an inode table. Data blocks are where the file data is actually stored. When a file is to be stored, the kernel will locate however many blocks are needed to store the file. These data blocks may or may not be adjacent to each other. The kernel will then store the information within these data blocks.

Directories

In Unix, a directory is a special type of file. A directory contains all of the names of the files stored within it. To be able to view the files of a directory with their respective inode numbers is to do an ls -1ia.

bh011695@lab46:~$ ls systemsProg -1ia
573673308 .
 66461891 ..
573676539 .swp
573654795 Output.txt
573673286 cli
573673302 cli.c
573655652 filesandargs.c
573655709 fork.c
573655682 fork2.c
573654788 fprintf
573655613 fprintf.c
573655696 fscanf.c
573655614 getopt.c
573655620 mtest
573655617 mtest.c
573655706 output.txt
573655618 screentest.c
573655707 stringmanip.c

Looking at the output, there is a big list of numbers followed by file names. For instance inode 573655707 is associated with stringmanip.c. All of the meta data for stringmanip.c is stored within this inode.

Symbolic links or just links for short are very much like a shortcut in windows. A link is a file that points to another file. The reason for this is because multiple files can have the same inode number.

bh011695@lab46:~/ASM_6502$ ln helloWorld.asm newHi.asm
bh011695@lab46:~/ASM_6502$ ls -1ia
553450825 .
 66461891 ..
556055369 0x8.65s
556055370 0x9.65s
556055378 0xA.65s
553450824 ASM_EOCE.txt
556055371 helloWorld.asm
556055371 newHi.asm

If we look at the output of ls after doing the ln, we see that helloWorld.asm and newHi.asm both share the same inode number.

DATA Objectives

Objective 1

Choose the appropriate data structure for solving a given problem. Apply the data structure that would be best suited for solving some problem.

Method

I've used our stack implementation from earlier to be able to solve the palindrome problem.

#include "LinkedList.h"
#include <cstdio>
#include <cstdlib>
 
void push(char, LinkedList&);
char pop(LinkedList&);
 
int main()
{
	LinkedList stack;
	char *string, *start, c;
	int unequal = 0;
 
	string = (char*) malloc(sizeof(char)*50);
	start = string;
 
	printf("Enter a string: ");
	scanf("%s", string);
 
	printf("Your string is %s.\n", string);
 
	while (*string != '\0') 
		push(*string++, stack);
 
	string = start;
 
	while (stack.length() > 0 ) {
		if (pop(stack) != *string++)
			unequal = -1;
		stack.del(0);
	}
 
	string = start;
 
	if (unequal == 0)
		printf("%s is a palindrome.\n", string);
	else	
		printf("%s is not a palindrome.\n", string);
 
	return 0;
}
 
void push(char value, LinkedList &s)
{
	s.insert(value, 0);
}
 
char pop(LinkedList &s)
{
	return s.getCharVal(0);
}

Measurement

lab46:~$ ./palindrome
Enter a string: noon
Your string is noon.
Noon is a palindrome

./palindrome
Enter a string: lair
Your string is lair.
lair is not a palindrome.

Analysis

Reflect upon your results of the measurement to ascertain your achievement of the particular course objective.

  • How did you do? - It works pretty well. There's no need to worry about reversing the word yourself since it comes off the stack in reverse order.
  • Room for improvement? - It was a pretty simple example, so there's probably not much you can do to enhance it.
  • Could the measurement process be enhanced to be more effective? - Again, since it was a fairly simple example there's not much more to measure. The only two things to be concerned with are if it is a palindrome or if it isn't.
  • Do you think this enhancement would be efficient to employ?
  • Could the course objective be altered to be more applicable? How would you alter it? - You could possibly assign a certain problem to a certain data structure.

SYSPROG Objectives

Objective 1

Better understand file I/O for efficient data processing. Be able to read and write using C functions.

Method

I've created a bit of a file copying program. It opens an input file, reads the character data into it, and then copies it into a new file.

#include <stdio.h>
#include <stdlib.h>
 
int main(int argc, char** argv)
{
	char *filePath;
	int inChar;
	FILE *inFile, *outFile;
 
	filePath = (char*) malloc(sizeof(char) * 100);
 
	if (argc != 2) {
		printf("Path to file: ");
		scanf("%s", filePath);
		inFile = fopen(filePath, "r");
		outFile = fopen("fileCopy.txt", "w");
	} else {
		inFile = fopen(argv[1], "r");
		outFile = fopen("fileCopy.txt", "w");
	}
 
	while ((inChar = fgetc(inFile)) != EOF)
		fputc(inChar, outFile);
 
	fclose(inFile);
	fclose(outFile);
 
	return 0;
}

Measurement

It does what it's supposed to. It reads input from a file and then outputs it into a new file.

Analysis

Reflect upon your results of the measurement to ascertain your achievement of the particular course objective.

  • How did you do? - Not bad.
  • Room for improvement? - Yeah, there are multiple ways to do what I did there. I could have employed those as well.
  • Could the measurement process be enhanced to be more effective? I think it would definitely benefit to use the other file IO functions.
  • Do you think this enhancement would be efficient to employ? It would certainly make one more versatile on the subject.
  • Could the course objective be altered to be more applicable? How would you alter it? You could specifically ask for to demonstrate file IO using a file descriptor and using a file pointer.

Experiments

* Vs &

Question

I had some issues with part of the project in discrete. I was having a little confusion as passing a pointer. I'm used to either passing a normal variable by value or passing by referencing using &. So, the question is, aren't sending a pointer and passing by reference the same thing?

Resources

Well, from what I know about pointers, when you pass one to a function you're passing a memory address. When you pass by reference, you pass the memory address of the variable that you're passing, a pointer. http://www.cplusplus.com/forum/beginner/7117/

Hypothesis

Passing a variable by reference and passing a pointer to a variable will achieve the same result.

Experiment

How are you going to test your hypothesis? What is the structure of your experiment?

Data

#include <cstdio>
 
void first(int&);
void second(int*);
 
int main()
{	
	int i = 25;
	int *j = &i;
 
	first(i);
	second(j);
 
	return 0;
}
 
void first(int& i)
{
	printf("%d\n", i);
}
 
void second(int *i)
{
	printf("%d\n", *i);
}
lab46:~$ ./exp1
25
25

Analysis

Based on the data collected:

  • was your hypothesis correct? - Yes
  • was your hypothesis not applicable? - No
  • is there more going on than you originally thought? There is one thing that I didn't think about. A pointer can be set to NULL where a variable sent by reference cannot.

Conclusions

For the most part, sending a pointer to a function and passing a variable by reference isn't much difference. The only real difference between the two of these is the ability to set a pointer to NULL which doesn't exist when passing by reference.

String Vs String

Question

Something I've wondered in C++ there is a string type however, in C we create a pointer or an array of characters. Are these two things identical in size?

Resources

The two should be the same size. A C string should be one byte times the number of elements and the string type, unless there's added overhead, should be the same size. However, I want to say that I've tested this before and the two variables were of different sizes. Thought I can't quite remember.

Hypothesis

A C string and a C++ string type variable should be the same size in bytes if they are of the same length.

Experiment

How are you going to test your hypothesis? What is the structure of your experiment?

Data

int main()
{ 	
	char *s1 = "cats";
	string s2 = "cats";
 
	printf("s1 is %d bytes\n", sizeof(s1));
	printf("s2 is %d bytes\n", sizeof(s2));
 
	return 0;
}
lab46:~$ ./size
s1 is 4 bytes
s2 is 4 bytes

Analysis

Based on the data collected:

  • was your hypothesis correct? Yes
  • was your hypothesis not applicable? No
  • is there more going on than you originally thought? (shortcomings in hypothesis) There is something I noticed. This brings up a new question. A null terminator, though unseen, is tacked onto the end of the C string. That seems like it should have made the string 5 bytes but it was only 4. Why would this be?

Conclusions

It does turn out that both the C String and the string type are the same size if they are of the same length. However, I'm NOT sure as to why they're only four bytes. “cats\0” should be 5 bytes I would think.

How Deep is the Well?

Question

The SysProg book had an experiment. Create a shell script that seems to recursively create directories. Even a second after there should be lots and lots of directories. Kind of like sticking a mirror in front of another mirror and looking at it. The question is, how do you delete them?

Resources

The book says that it shouldn't take long to create a directory tree that will go beyond the abilities of most commands that work on directory trees.

Hypothesis

There won't be a simple command that deletes this tree. Rm will not delete a directory if it contains any other files. Rmdir works very much the same and lacks a recursive option. Neither of these should work.

Experiment

The experiment will be to use a shell script to create a gigantic directory tree. After the tree has been created, check the number of sub-directories with ls. Confirm this by cd-ing through the tree. Lastly, try to delete the tree with a fairly simple command.

Data

Create a huge file tree

#!/bin/bash
while true
  do
    mkdir deep-well
    cd deep-well
   done

Doing an ls -lh gives us the following output on our new directory tree

drwxr-xr-x 3 ubuntu ubuntu 2.0k 2011-10-01 12:57 deep-well

I strolled through the tree and found the directory count, 3, in the ls was quite wrong. So, if that doesn't work, will it be possible to delete the tree using standard rm and rmdir. Both of these result in failure.

ubuntu:~$ rm deep-well/
rm:cannot remove `deep-well/': Is a directory
ubuntu:~$ rmdir deep-well
ubuntu:~$ rmdir: failed to remove `deep-well': Directory not empty

Now what happens when we add the force and recursive options to rmdir?

ubuntu@ubuntu:~/scripts$ ls
2Fake.sh    deepWell.sh    guessing_game.sh  random_num_generator.sh
anagram.sh  dialog.sh      jumble2.sh        test_jumble.sh
array.sh    dice_game.sh   jumble.sh         yrProd.sh
auto_draw   fake_work.sh   mousescroll.sh
deep-well   ghostarray.sh  palindrome.sh
ubuntu@ubuntu:~/scripts$ cd deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/
ubuntu@ubuntu:~/scripts/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well$ pwd
/home/ubuntu/scripts/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well
ubuntu@ubuntu:~/scripts/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well/deep-well$ cd ~/scripts
ubuntu@ubuntu:~/scripts$ rm -rf deep-well/
ubuntu@ubuntu:~/scripts$ ls
2Fake.sh    deepWell.sh   ghostarray.sh     mousescroll.sh           yrProd.sh
anagram.sh  dialog.sh     guessing_game.sh  palindrome.sh
array.sh    dice_game.sh  jumble2.sh        random_num_generator.sh
auto_draw   fake_work.sh  jumble.sh         test_jumble.sh
ubuntu@ubuntu:~/scripts$ 

Analysis

Based on the data collected:

  • was your hypothesis correct? - No
  • was your hypothesis not applicable? - Yes
  • is there more going on than you originally thought? (shortcomings in hypothesis) I don't think so.
  • what shortcomings might there be in your experiment? - I may need to run the shell script for, at least, a few seconds.
  • what shortcomings might there be in your data? - I should, although I'm not sure how possible to dive to the bottom of the tree.

Conclusions

It is possible to delete a fairly large directory tree with a simple command line. However, it would be worth retesting while keeping track of how long the shell script runs for, 5 seconds, 10 seconds, etc. It is possible that it's simply not running long enough. There may be a point where commands will no longer be able to do any kind of operations on the tree.

Part 2

Entries

October 4, 2011

I got an array based stack implemented. Looking at the implementation on wikipedia was a bit confusing. I had some problems getting it to compile and to some degree it could be hard to follow So, in the end, I just decided on my own implementation. Surprisingly, the array based stack seemed more difficult to write than the linked list based stack. This is probably because most of the grunt work in the linked list based stack gets done in the linked list class. With the array I had to start from scratch. Also, with the linked list version, whenever you push onto the stack, you're just creating a new start. In the array version, you need to move all the elements up the stack and then you can add the new value. Similarly, when you pop a value off, you shit all of other values down by one. The only thing I question at this point is manually setting the value of top to 0 after declaring and allocating memory for the stack. I'm sure there's a better way to do it. The compiler wasn't very happy when I initialized it within the struct. I'll do some snooping around.

October 14, 2011

So, I've done a struct based linked list. A less OO approach to it I guess is what I should say. However, I've run into a problem with deleting nodes. The rest of the functionality seems to work fine. It appears that delete, in its entirety, is broken. Upon trying to delete a node in the middle of the list a -1 is returned from the delete function, an error status. …okay, now I'm upset. I'm pretty certain I found it. After beating my head over it most of Friday evening, I found it. The whole purpose of this entry was to help track down there error by logging its behavior. Well, I guess it worked. However, I won't bet that it works 100% just yet. Tests are required. Horrible tests. Okay, test one: delete node from the middle of the list is successful. Now let us try to delete from the beginning of the list. And failure… Loose ends apparently aren't tied up. After deleting node 0 and trying to retrieve the value of the new node 0 we get seg fault hell. Okay, before doing anything else let us try deleting from the last node in the list. Everything here acts okay UNLESS you make an attempt to access the node that was deleted. IE if you have 3 nodes and you delete the last node, node 2 and then you try to access that node. Comparing my code to the code we originally wrote in class, the only difference that I see is the order that some of the things are done. I can't imagine that this should matter though. I'll try it anyway. Just not right now as class will be starting in a few minutes. I require snacks!

October 24, 2011

Somehow I end up doing one of these every ten days. Strange… I finished the link list library. I might throw in some added functionality in the future. For right now I've got the following functions: create, append, insert, deleteNode, deleteList, getLength, getValue, and copy. The linked list library in Java has functionality to dump the values of list into an array. I may or may not add that in the future. It could be useful. I think from here, I'm going to start working on the stack library. I'll use the java library as guide. It doesn't seem have a lot of extra functionality. One of the functions already exists in my linked list library, the search function. It also has a function to check if the stack is empty which wouldn't be very hard to implement. Other than that it just has your standard p functions: push, pop, and peek.

I'm a bit stuck on the upgrade to the jumble solver. Once it does the final check on the two strings, it always says the two strings are unequal. I've done some debugging on it. I've printed out and compared the entire strings and I've checked them character by character and it can still show them as equal but it never executes the if statement. My only guess is that it reads past the null terminator and keeps comparing. I have no idea how to fix that, though. Not yet, anyway.

October 31, 2011

Well, there's no October 34 and even if there was the opus is due tomorrow so that means i'm here tonight. I've been doing quite a bit of playing lately. My linked list based stack has been giving me some problems. I think I may be having some issues with naming conventions. I'm not really sure exactly how I should structure it. Chances are I'm making too big of a deal out of things. Really, a stack is nothing more than a special case linked list. My original thought was to simply have a line that defines List as Stack. I've been working through the examples in chapter 7 of the systems programming book. It gets into creating a pong type game using the curses library. This is very much like what I've been playing with in python. It'll be neat to see similarities and differences between the two implementations.

data Topics

Stacks (array-based, linked-list based)

Linked List Based

Think of a linked list based stack as a vertical linked list. Every time a new value is added to the stack, you “slide” it under the the previous value in the stack. Lets say that we want to create a stack and place the values 1, 2, 3, 4 onto the stack. Each time we add a new value, it would look as follows.

          [4]
      [3] [3]
  [2] [2] [2]

[1] [1] [1] [1]

The difference between a linked list based stack and an array based stack is that we don't necessarily know ahead of time how big our stack must be. For instance, if we want to test a string to see if it is a palindrome. We move each character to the stack, pull them off the stack and compare.

Array Based

In an array based list, the bottom of the list is at list[0] and the top of the list would be at list[size - 1]. Size is whatever the length of the list is. Think of this like a character string. The array of characters can be of any length but the null terminator, \0, can bee used as a means of stopping. In our case size works like the null terminator, a means of stopping in our list.

#include <stdio.h>
#define STACK_SIZE 50
 
struct Stack {
	int values[STACK_SIZE]; //The stack
	int top;				//Pos of the top of the stack
};
typedef struct Stack Stack;
 
int push(Stack*, int);
int pop(Stack*);
int peek(Stack*);
 
int main()
{
	Stack *stack;
	int input;
 
	//Allocate memory for our stack and initialize top
	stack = (Stack*) malloc(sizeof(Stack));				
	stack -> top = 0;
 
	//Get some input and store it on the stack
	printf("Enter a number (-1 to quit): ");
	scanf("%d", &input);
 
	while (input != -1 && stack -> top <= STACK_SIZE) {
		push(stack, input);
		printf("Enter a number (-1 to quit): ");
		scanf("%d", &input);
	}
 
	//Peek at the top of the stack
	printf("Top of the stack: %d\n\n", peek(stack));
 
	//Pop the contents of the stack and print them
	while (stack -> top > 0) {
		printf("%d\n", pop(stack));
	}
 
	return 0;
}
 
int push(Stack *sp, int val)
{
	/*Push values onto the stack. If the stack
	size is reached return a -1, otherwise
	return 0.*/
	int x;
 
	if (sp -> top > STACK_SIZE) { //Our stack has reached its size limit
		fprintf(stderr, "Stack full!\n");
		return -1;
	} else if (sp -> top == 0) { //The stack is empty
		//Move val onto the stack
		sp -> values[0] = val; 
		//increment the top of the stack
		sp -> top++;		   
		return 0;
	} else {	//Our stack is still growing
		for (x = sp -> top; x >= 0; x--)
			//Push all the values on the stack up by one
			sp -> values[x + 1] = sp -> values[x]; 
		//Push val onto the stack
		sp -> values[0] = val;	
		//Incrememnt the top of the stack
		sp -> top++;
		return 0;
	}
}
 
int pop(Stack *sp)
{
	/*(Pop values from the stack. If the stack
	reaches 0, return a -1, otherwise return the
	value from the bottom of the stack.*/
	int val, x;
 
	//pull the bottom value from the stack
	val = sp -> values[0];
 
	if (sp -> top == 0) { //Stack is now empty
		fprintf(stderr, "Stack empty!\n");
		return -1;
	} else { //The stack is not empty
	for (x = 0; x < sp -> top; x++)
		//Pull all the values down the stack by one
		sp -> values[x] = sp -> values[x + 1];
	//Decrememnt the top of the stack
	sp -> top--;
 
	return val;
	}
}
 
int peek(Stack *sp)
{
	//Returns the value at the top of the stack
 
	return sp -> values[sp -> top - 1];
}

Pushing

Pushing involves adding a new value to stack and “Push” any other items to higher positions on the stack.

lab46:~$ ./arrayStack
Enter a number (-1 to quit): 1
Enter a number (-1 to quit): 2
Enter a number (-1 to quit): 3
Enter a number (-1 to quit): 4
Enter a number (-1 to quit): 5
Enter a number (-1 to quit): -1
Top of the stack: 1

Here we push the values 1 through 5 onto the stack. After we've done this, using the peek function we print the value at the top of the stack.

for (x = sp -> top; x >= 0; x--)
	//Push all the values on the stack up by one
	sp -> values[x + 1] = sp -> values[x]; 
	//Push val onto the stack
	sp -> values[0] = val;	

Here's where most of the work is done when we push a value to the stack. We move all of the values on the stack up one position and then insert the new value at position 0 of the array.

If we were using a linked list, when we push an item onto the stack, we would create a new node. This node would become the new start who's prev will be pointing to NULL and who's next will be pointing to the previous start.

Popping

Popping, as expected, is the opposite of pushing. We get the value from the bottom of the stack and then we shift every other value down one position.

for (x = 0; x < sp -> top; x++)
		//Pull all the values down the stack by one
		sp -> values[x] = sp -> values[x + 1];

Fully demonstrating the array based stack program with the below example.

lab46:~$ ./arrayStack
Enter a number (-1 to quit): 1
Enter a number (-1 to quit): 2
Enter a number (-1 to quit): 3
Enter a number (-1 to quit): 4
Enter a number (-1 to quit): 5
Enter a number (-1 to quit): -1
Top of the stack: 1

5
4
3
2
1

In a linked list based stack, when we pop an item off the stack, we would set our temp to start and start to temp's next. Then temp's prev would be set to NULL and temp would be de-allocated.

Top

Top refers to the top of a stack. The top of the stack is simply the entry or exit for any data being pushed onto the stack. Whenever some piece of data is first pushed onto to the stack, it is considered to be at the top. No item can be popped from the stack unless it is at the top. Similarly, no item can be pushed onto the stack without, first, being at the top position.

Overflow condition

Whenever a stack becomes full and no more data can be pushed onto the stack, this is referred to as an overflow condition or overflow state.

#include <stdio.h>
#include "ArrayStack.h"
 
int main()
{
	int i;
	Stack *myStack;
 
	for (i = 0; i < 55; i++) {
		if (i < STACK_SIZE) {
			push(myStack, i + 1);
			printf("%d pushed to stack\n", i + 1);
		}
		else 
			printf("Stack overflow state!\n");
	}
 
	return 0;
}

As can be seen below, the code attempts to push 55 objects onto the stack. The problem is that the stack can only hold 50 items. The last 5 will reach an overflow state and not be added.

stackOverflow.exe
Process started >>>
1 pushed to stack
2 pushed to stack
3 pushed to stack
4 pushed to stack
5 pushed to stack
6 pushed to stack
7 pushed to stack
8 pushed to stack
9 pushed to stack
10 pushed to stack
11 pushed to stack
12 pushed to stack
13 pushed to stack
14 pushed to stack
15 pushed to stack
16 pushed to stack
17 pushed to stack
18 pushed to stack
19 pushed to stack
20 pushed to stack
21 pushed to stack
22 pushed to stack
23 pushed to stack
24 pushed to stack
25 pushed to stack
26 pushed to stack
27 pushed to stack
28 pushed to stack
29 pushed to stack
30 pushed to stack
31 pushed to stack
32 pushed to stack
33 pushed to stack
34 pushed to stack
35 pushed to stack
36 pushed to stack
37 pushed to stack
38 pushed to stack
39 pushed to stack
40 pushed to stack
41 pushed to stack
42 pushed to stack
43 pushed to stack
44 pushed to stack
45 pushed to stack
46 pushed to stack
47 pushed to stack
48 pushed to stack
49 pushed to stack
50 pushed to stack
Stack overflow state!
Stack overflow state!
Stack overflow state!
Stack overflow state!
Stack overflow state!

Underflow State

An underflow state is the opposite of an overflow. With an underflow, there are no more data left on the stack to manipulate, yet we may be trying to perform a pop operation.

LIFO or FIFO

LIFO refers to Last In First Out. This is usually used in conjunction with with stacks. There is only one entrance or exit to a stack. So, if you were to push 1, 2, 3, 4, 5 to a stack the last in would be the first out, 1.

FIFO refers to First In First Out. FIFO is usually used with queues. In a queue there is an entrance at the beginning and an exit at the end. Unlike stacks where the entrance and exit are the same point. Again, using 1 2 3 4 5 as an example. The first in would be 5, and therefore the first out.

Queues: Array Based Vs Linked List Based

Queues are much like stacks but with a few important differences. First, a stack grows up queues grow out. When you think of queues, imagine that really big line at the Wal-Mart check out because the geniuses decided to only have 2 registers open out of about 14. The first one into the line will be the first one out.

In an array based queue, once you have x many items on your queue, you have the ability to dequeue them. When you dequeue them, the idea will be to keep track of how many items are on the queue. So, again, if we have x items and we perform a dequeue operation, we will have x - 1 items on the list. We won't actually remove the dequeued item, however, right now we will decrease our queue size. This can be thought of like a string. There may be information after our null terminator, but we don't care about that so the null terminator stops us from accessing it.

//future code to be added

A linked list based queue would be very similar however, there will be one major difference. Here, we're not limited to a certain size like we are with an array. We can make our queue grow and shrink as we please. Much like how we made our stack grow and shrink. In our array based queue, we did not delete data as we popped it off. With a linked list based queue, we have the ability to delete data once we're done with it.

Enqueing

Enqueing information to a queue is identical to a push operation on a stack. When using an array, all the elements will get moved up one position in the array and then the new data will be added to the beginning of the queue. With a linked list queue, simply add a new node to the beginning of the list and move the data into it.

Dequeuing

Dequeuing is the act of removing some piece of data from a queue. Since a queue, is first in first out, the oldest piece of data on the queue is what will get dequeued. With an array queue , an index or a pointer is needed to keep track of the end of the queue. Once the data is dequeued, that index or pointer will be decremented. In a linked list queue, once the data has been dequeued simply delete the final node in the list.

Overrun and Underrun conditions

An ovverrun condition occurs when trying to enqueue data onto a queue that is already full. This would really only be a problem with an array based queue since a linked list can grow or shrink as needed. An underrun condition will occur when trying to dequeue a queue with no data on it. Again, this would only be an issue with an array based stack.

#include <stdio.h>
#include <stdlib.h>
#include "ArrayStack.h"
 
int main()
{
	int i;
	Stack *myStack;
 
	myStack = (Stack*) malloc(sizeof(Stack));
	myStack -> top = 0;
 
	for (i = 0; i < STACK_SIZE; i++)
		push(myStack, i * 2);
 
	for (i = STACK_SIZE; i > -5; i--)
		printf("%d\n", pop(myStack));
 
	return 0;
}
lab46:~$ ./stackUnderflow
98
96
94
92
90
88
86
84
82
80
78
76
74
72
70
68
66
64
62
60
58
56
54
52
50
48
46
44
42
40
38
36
34
32
30
28
26
24
22
20
18
16
14
12
10
8
6
4
2
0
Stack empty!
-1
Stack empty!
-1
Stack empty!
-1
Stack empty!
-1
Stack empty!
-1

LIFO or FIFO

LIFO refers to Last In First Out. This is usually used in conjunction with with stacks. There is only one entrance or exit to a stack. So, if you were to push 1, 2, 3, 4, 5 to a stack the last in would be the first out, 1.

FIFO refers to First In First Out. FIFO is usually used with queues. In a queue there is an entrance at the beginning and an exit at the end. Unlike stacks where the entrance and exit are the same point. Again, using 1 2 3 4 5 as an example. The first in would be 5, and therefore the first out.

sysprog Topics

Device Files

In UNIX, devices all have an associated file. Devices can be written to and read from just like a file. Every device has a filename, an inode number, an owner, permissions, and a time that it was last modified. However, every device does not necessarily support every file operation. For instance a mouse file does not support the write system call and a terminal does not support lseek.

Race Condition

A race condition is a big problem in systems programming. A race condition occurs when a record gets overwritten and is lost due to timing issues. Lets say that two users are about to write to the same file. The first person seeks the end of file and then it is the second user's slice of time and they seek the end of file. Now the first person write to their EOF position. Once they're done the second user writes to their EOF position which happens to be the sames as the first person's.

Another possible race condition is a file being created at the same time by two different user. Combining the O_CREAT and O_EXCL flags into an atomic operation is supposed to clear up this condition. However, this doesn't work 100% of the time.

Atomic Operation

lseek and write are two separate processes, which the kernel is free to interrupt in between the two. When the O_APPEND flag is set the kernel combines these two into a single operation called an atomic operation.

Streams

The basic idea of the streams model of data flow is that the data will be processed in some order. However, any module in this process can be removed or disabled. If what your terminal driver supports is not to your liking, it is possible to write and install your own modules. You write your new module to the specifications of STREAMS. Then you would use certain system calls to install it where you like.

Connection Control

Connection control refers to how one may interface with a device such as the terminal. Connections to devices share similarities with to connections to disk files. Both can be interacted with using standard file system calls such as open, read, write, close, and lseek. File permissions control access to these devices the same way they would disk files.

Blocking Vs Nonblocking Input

Blocking input occurs when canf() or getchar() are called. After they're called, the program will come to a halt until it gets what it wants, data entered into the keyboard. Blocking is a property of any open file which can be turned off by using fcntl() or open() to turn on the O_NDELAY or the O_NONBLOCK flag for the file descriptor. Once blocking has been disabled, it is possible to enter input into the keyboard without pressing the enter key.

Singals

Signals are small messages that sent from the kernel to a running process. Think of it like a criminal way back when running off and a cop blows their whistle. If your program does something you don't want it to, you blow your whistle to tell it to halt. The process can tell the kernel how it wants to respond when it receives a signal.

#include <signal.h>
#include <stdlib.h>                                                                                                                                          
 
int main()
{
    void f(int);
    int i;
 
    signal(SIGINT, f); 
 
    for (i = 0; i < 10; i++) {
        printf("hello\n");
        sleep(1);
    }   
 
    return 0;
}
 
void f(int signum)
{
    char choice;
 
    printf("Interrupted! Quit y/n?");
    scanf("%c", &choice);
 
    if (choice == 'y' || choice == 'Y')
        exit(1);
}
lab46:~/c_programs$ ./sigmdemo
hello
^CInterrupted! Quit y/n?N
hello
hello
^CInterrupted! Quit y/n?hello
hello
hello
^CInterrupted! Quit y/n?y
lab46:~/c_programs$

Event Driven Programming

Event driven programming involves writing programs which rely on actions that are not necessarily “scripted”. A good example would be the commands entered into the Vi Editor. When you press i to enter insert mode, an event has occurred.

Alarms/Timers

An alarm counts down from a specified time and sends the SIGALRM signal to the process when the timer reaches 0.

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
 
int main(int argc, char **argv)
{
	void wakeUp(int);
	int i;
 
	if (argc != 2) {
		exit(0);
	} else {
		i = atoi(argv[1]);
		printf("Sleeping for %d seconds.\n", i);
		signal(SIGALRM, wakeUp);
		alarm(i);
		pause();
	}
 
	return 0;
}
 
void wakeUp(int i)
{
		printf("Waking up...\n");
}
brad@brad-desktop:/media/50E9-B47B/Notepad++/Programs$ ./alarm 5
Sleeping for 5 seconds.
Waking up...

Reentrant code, critical sections

A function is reentrant if it can be called when it is already active and will not cause problems. It can be interrupted during its execution and called again safely before the previous version of it completes executing. Once the new version finishes its execution the previous version will correctly finish its execution.

A critical section is a piece of code that is accessing a shared resource, a list or a stack, if interruptions occur bad data is produced. In other words the data structure cannot be accessed concurrently.

Asynchronous IO

With asynchronous IO, other processes are permitted to finishing processing before the transmission has finished. A signal will be sent when the input arrives and it will be possible to do other things until a signal arrives to alert that input is available.

Terminal Control and Signals

A signal is a way of flagging down a process because a certain occurred. For instance, we may want to perform some certain action when the user presses CTRL-C and sends an interrupt signal. UNIX contains several signals for various purposes. kill -l displays all of the different signals on a UNIX system.

brad@dhcp-181:/media/50E9-B47B/Notepad++/Programs$ kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX	

data Objective

Objective

Compare and contrast the costs and benefits of dynamic and static data structure implementations. Discuss the trade offs between linked list based data structures and array based data structures.

Method

I'm going to discuss some of the similarities and differences between the two types of data structures, statically allocated and dynamically allocated, along with their advantages and disadvantages.

Measurement

With static allocation, using array based stacks and queues, the data structure is allocated when the program begins execution. Because of this the size of the data structure must be known ahead of time. This means that the data structure can only grow to a certain size. However, it does means that all of the values in the data structure are adjacent in memory which gives a boost in performance vs dynamic allocation.

In dynamic allocation, a linked list based data structure, memory is allocated for the structure during execution. We don't need to know how many elements our structure will have. It could have 10 elements or it could have 100 or even 1000 or more. However, on the down side, the values are not stored adjacently in memory which means there's no straight path through the list. This would be like walking to a store that's down the street but instead of going straight down the street you take several turns before you get there.

Analysis

It would help to have some data on how much of a time gap there is between statically allocated structures and dynamically allocated structures. This, indeed, sounds like an experiment. That's our next step.

sysprog Objective

Objective

design programs that handle signals

Method

I have a program that when you press CTRL-C it grabs the SIGINT and asks the user if they really want to quit.

Measurement

#include <signal.h>
#include <stdlib.h>                                                                                                                                          
 
int main()
{
    void f(int);
    int i;
 
    signal(SIGINT, f); 
 
    for (i = 0; i < 10; i++) {
        printf("hello\n");
        sleep(1);
    }   
 
    return 0;
}
 
void f(int signum)
{
    char choice;
 
    printf("Interrupted! Quit y/n?");
    scanf("%c", &choice);
 
    if (choice == 'y' || choice == 'Y')
        exit(1);
}
lab46:~/c_programs$ ./sigmdemo
hello
^CInterrupted! Quit y/n?N
hello
hello
^CInterrupted! Quit y/n?hello
hello
hello
^CInterrupted! Quit y/n?y
lab46:~/c_programs$

Analysis

I could have added more signals into the program. Implementing a new action depending on the signal given. It wouldn't be hard to implement. It would only need a few additional callbacks to handle the additional signals. I would alter it to incorporate two or more signals and perform different operations depending on the signal that is received.

Experiments

Experiment 1

Question

Is the executable file using the std namespace larger than an executable file that omits and instead prepends std:: when necessary? This is under the assumption that using the namespace pulls in more code than not using it.

Resources

http://www.cplusplus.com/forum/beginner/14325/ http://www.cplusplus.com/forum/beginner/9181/

As far as I can tell it's more of a problem having multiple objects being referred to by the same name rather than the size of a program.

Hypothesis

From what I've read, my original thought was wrong. It's simply a naming convention issue. The size of the executable should not be affected by using or not using a namespace.

Judging from the above links, the objects will all still be present in the executable, however, their scope will be different depending on whether the programmer decides to use or not use the namespace. This means the actual file size should not be affected.

Experiment

I'm going to write a hello, world program in C++, first, without including the std namespace. I'll compile it and do an ls -lh to get the size of the executable. Then I will rewrite the program to include the namespace and, again, do an ls -lh to get the size of the executable and compare the two of them.

Data

Without including the std namespace
brad@brad-desktop:/media/50E9-B47B/Notepad++/Programs$ ls -lh | grep 'namespaceTest'
-rwxr-xr-x 1 brad brad 7.7K 2011-10-27 12:31 namespaceTest

Including the std namespace

brad@brad-desktop:/media/50E9-B47B/Notepad++/Programs$ ls -lh | grep 'namespaceTest'
-rwxr-xr-x 1 brad brad 7.7K 2011-10-27 12:59 namespaceTest

Analysis

Based on the data collected:

  • yes, the hypothesis was correct.
  • There does not seem to be more going on than originally thought.
  • I may want to try something bigger than hello, world. I could write a program that also uses stdio and file io to use more than just cout and endl.
  • using the -h argument to ls makes the file size human readable at a kb level. I could have left that off to get the actual number of bytes, which would show even a slight difference in the file size.

Conclusions

Using the std namespace may not give you a huge executable incorporating things that you never use, however, it can cause variable or function names to butt heads“. myNamespace::x and x aren't the same thing but x and x are, which would certainly cause problems.

Experiment 2

Question

When working with large data structures, is there a large gap in execution time between statically allocation and dynamic allocation?

Resources

Collect information and resources (such as URLs of web resources), and comment on knowledge obtained that you think will provide useful background information to aid in performing the experiment.

Hypothesis

Considering that there is not a straight path from value to value in memory with dynamic allocation, when working with larger structures there should be a noticeable time difference when compared with a statically allocated structure of the same size.

Experiment

I'm going to create two different programs. One program will contain an array with 1024 integer elements that will be populated using a for loop. The other program will contain a linked list containing 1024 integer elements that will also be populated using a for loop. I will use the unix time utility to record each program's execution time and compare the times of each program.

Data

Static Allocation
int main()
{
	int intArray[1024], i;
 
	for (i = 0; i < 1024; i++) {
		intArray[i] = i + 1;
	}
 
	return 0;
}
brad@brad-desktop:/media/50E9-B47B/Notepad++/Programs$ time ./staticTest

real	0m0.001s
user	0m0.000s
sys	0m0.000s
Dynamic Allocation
#include "List.h"
 
int main()
{
	List *myList;
	int i;
 
	myList = create();
 
	for (i = 0; i < 1024; i++) {
		appendToList(myList, i + 1);
	}
 
	return 0;
}
brad@brad-desktop:/media/50E9-B47B/Notepad++/Programs/LinkedList$ time ./dynamicTest

real	0m0.002s
user	0m0.000s
sys	0m0.000s

Analysis

Based on the data collected:

Using the linked list did take longer than the array as expected. However, the time was very small. I would like to add a few things to see the difference in execution time over different intervals. I would document that difference in times over different allocation sizes. Starting with 1000 elements moving to 10000 elements in intervals of 1000 elements. This would give a much better idea as to the difference in execution time.

Conclusions

What can you ascertain based on the experiment performed and data collected? Document your findings here; make a statement as to any discoveries you've made.

Retest

State Experiment

I'm retesting Nick Sano's void pointer experiment. Is it possible to use a void pointer to pointer to a char and then to point to an int?

Resources

Nick seems to have a pretty good idea on void pointers. However, it is not possible to dereference a void pointer. http://stackoverflow.com/questions/692564/concept-of-void-pointer-in-c-programming

Hypothesis

Nick believes that a void pointer could be used so that it would not be necessary to declare a pointer of a certain type. Yes, I do believe that his hypothesis is adequate in what he's attempting to discover.

  • Do you feel their hypothesis is adequate in capturing the essence of what they're trying to discover?
  • What improvements could you make to their hypothesis, if any?

Experiment

I would have been a little more specific. I think what Nick was shooting for was to try, for example, to replace the int value in our node structure with a generic void pointer and change it depending on what type of data we'd like to put in our node structure. However, this is not entirely clear.

Data

Publish the data you have gained from your performing of the experiment here.

Analysis

int main()
{
    void *vPtr;
    int i = 25; 
    char c = '#';
 
    vPtr = (int*)&i;
    vPtr = (char*)&c;                                                          
 
    return 0;
}
brad@brad-desktop:/media/50E9-B47B/Notepad++/Programs$ gcc voidPointer.c -o voidPointer
brad@brad-desktop:/media/50E9-B47B/Notepad++/Programs$ 

Again, I would say that the hypothesis could be stated a bit more clearly. I did not attempt to dereference the pointer. I simply mangled it to be able to store different data types. However, all it would take as a simple print state me at the end of my code to attempt to do that.

Conclusions

It is possible to use a void pointer to point to different data types. The experiment did seem give Nick a better understanding of void pointers. However, it seems that there were certain aspects that he was not accounting for so it did not go quite as he had hoped. Actually… out of curiosity, I did attempt to add a printf statement and compile and the program compiles successfully. Also, after executing the program the desired effect appears to be achieved.

printf("%c\n", *(char*)vPtr);  

Adding that to the code appears to work just fine. It will print the '#' character.

Part 3

Entries

November 5th, 2011

I finally got my stack working. The problem took some time to sort out. The issue wasn't with the stack implementation really. It was more of a problem with the linked list. When I created the insert function for the linked list it was assumed that if you were inserting into the list, the list already existed. To push onto the stack I was using the insert function to insert at position 0, however, since there was technically no list as stack→first was set to NULL, which in the insert function was considered an error. I ended up backtracking further and further using the error codes to figure out what was going on. In the end, I added a simple check to the push function. If stack→first is NULL, use the append function instead of the insert function. Still, the real weird thing is that a lot of these problems didn't pop up at first. Initially, I wasn't using the create function to create a list to use as a stack. Everything worked fine until I was popping the last value from the stack and then it would print the value but segfault immediately after. Once I used to the create function, it segfaulted immediately. That's just some kind of strange voodoo witch doctor nonsense.

23 days later... =\

As of right now, I'm going through the algorithms book and reading through all of the different sorting algorithms. It's pretty cool that you can do the same thing that many different ways. Each algorithm has a varying level of efficiency and purpose. So far, I've covered the selection sort and the bubble sort. The bubble sort causes the larger values to “float” to the top and the smaller values to “sink” to the bottom of the list. In a selection sort, the idea is to search through the list to find the smallest value and swap it with the element in the current position in the list.

In systems programming, I just finished covering socket programming. Socket programming is much different in C than in it is in Java. There's a lot more lower level things to worry about than in Java.

import java.net.*;
import java.io.*;
 
class Server {
    public static void main(String args[]) {
        try {
            ServerSocket ss = new ServerSocket(65000);
            for (int x = 0; x < 5; x++) {                                       
                Socket s = ss.accept();
                PrintWriter p = new PrintWriter(s.getOutputStream());
                p.println("Connection number " + (x + 1) + ".");
                p.close();
            }
        } catch (Exception e) { System.out.println(e.toString()); }
    }   
}

That's all there is to it in java.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <time.h>
#include <string.h>
 
#define PORT_NUM 13000
#define HOST_LEN 256
#define oops(msg) {perror(msg); exit(1);}
 
int main(int argc, char **argv)
{
	struct sockaddr_in saddr;
	struct hostent *hp;
	char hostname[HOST_LEN];
	int sockID, sockFD, count = 0;
	FILE *sockFP;
	char *ctime();
	time_t theTime;
 
	//Get a socket
	sockID = socket(PF_INET, SOCK_STREAM, 0);
	if (sockID == -1)
		oops("socket");
 
	//Bind address to the socket
	bzero((void*)&saddr, sizeof(saddr));
	gethostname(hostname, HOST_LEN);
	hp = gethostbyname(hostname);
	bcopy( (void*)hp -> h_addr, (void*)&saddr.sin_addr, hp -> h_length);
	saddr.sin_port = htons(PORT_NUM);
	saddr.sin_family = AF_INET;
 
	if (bind(sockID, (struct sockaddr *)&saddr, sizeof(saddr)) != 0)
		oops("bind");
 
	//Allow incoming calls
	if (listen(sockID, 1) != 0)
		oops("listen");
 
	//main loop
	while (1) {
		sockFD = accept(sockID, NULL, NULL);
 
		if (sockFD == -1)
			oops("accept");
		sockFP = fdopen(sockFD, "w");
 
		if (sockFP == NULL)
			oops("fdopen");
 
		theTime = time(NULL);
		fprintf(sockFP, "The time here is..");
		fprintf(sockFP, "%s", ctime(&theTime));
		printf("Call number %d at %s", ++count, ctime(&theTime));
		fclose(sockFP);
	}
 
	return 0;
}	                                                                        

There's definitely more the user has to concern himself with here.

November 30, 2011

I got a start on the directory listing project. I've got some really basic functionality out of it which just consists of listing all the files within the directory except for the . files. Most of that comes from looking at what the book has and restricting . files really wasn't that hard. All you need to do is to check for whether or not the first character in the directory name is a . and if so don't display it. The book also says to alphabetize the listing, dump the listing to an array and run it through qsort. My problem here is that we have no real idea how many items will be in the directory. It could be 1 it could be more than 100. So, you could just make a 2 dimensional array of some size, however, you then could potentially have excess or insufficient space. I suppose you could also run through the items in the directory, count them, and then create a dynamic array for the number of items in the directory and then have the name length as a fixed size. As far as formatting goes i don't think that should be too difficult.

Month Day, Year

This is a sample format for a dated entry. Please substitute the actual date for “Month Day, Year”, and duplicate the level 4 heading to make additional entries.

As an aid, feel free to use the following questions to help you generate content for your entries:

  • What action or concept of significance, as related to the course, did you experience on this date?
  • Why was this significant?
  • What concepts are you dealing with that may not make perfect sense?
  • What challenges are you facing with respect to the course?

Remember that 4 is just the minimum number of entries. Feel free to have more.

data Topics

Computational Complexity

Computational Complexity focuses on classifying computational problems by their level of difficulty. The basic principle is that a problem is capable of being solved by a computer. A problem will be regarded as initially difficult if it requires a large amount of resources to solve. The resources in question can be disk space, memory usage, network resources, etc. This can help to establish the practical limits of current computers. This can also help to decide whether or not a particular problem can be solved with the given resources at hand.

Big-O, Theta, bounds

In computer science, Big-O notation is used to describe how an algorithm responds to different input sizes. This can be used to describe an algorithm's efficiency. It can be formally defined as f(x) = O(g(x)) as x approaches infinity. Big-O notation can sometimes be replaced by Big-Theta notation. Depending on the bounds you wish to describe will determine which notation would be used.

Selection Sort

Selection Sort Visual Example

In a selection sort the basic idea is to find the smallest value in a list and move it to the first position. From here we move tot he second position and search for the smallest value starting from this position and moving it to this position. After we find the smallest value and move it to the current position, we move to the next position and repeat.

#include <iostream>
#include <cstdlib>
 
using std::cout;
using std::endl;
 
void selectionSort(int[]);
const int ELEMENTS = 25;	
 
int main()
{
	srand(time(NULL));
	int values[ELEMENTS], j, temp, min;
 
	for (int i = 0; i < ELEMENTS; i++)
		values[i] = rand() % 100;
 
	cout << "Before: ";
	for (int i = 0; i < ELEMENTS; i++) 
		cout << values[i] << " ";
	cout << endl;	
 
	selectionSort(values);
 
	cout << "After: ";
	for (int i = 0; i < ELEMENTS; i++)
		cout << values[i] << " ";
	cout << endl;
 
	return 0;
}
 
void selectionSort(int v[])
{
	int minIdx, temp;
 
	for (int i = 0; i < ELEMENTS - 1; i++) {
		minIdx = i;
 
		for (int j = i + 1; j < ELEMENTS; j++) {
			if (v[j] < v[minIdx])
				minIdx = j;
		}
 
		if (minIdx != i) {
			temp = v[minIdx];
			v[minIdx] = v[i];
			v[i] = temp;
		}
	}
}
brad@Lucid-Lynx:/media/50E9-B47B/Notepad++/Programs$ ./selectSort
Before: 99 9 57 47 9 77 47 61 14 35 92 49 87 32 95 54 21 85 96 96 66 9 48 29 2 
After: 2 9 9 9 14 21 29 32 35 47 47 48 49 54 57 61 66 77 85 87 92 95 96 96 99 

Bubble Sort

The idea behind a bubble sort is that the higher values will rise to the top of the list while the lower values will sink to the bottom of the list. The list is sorted by checking adjacent array elements and if the element that is higher in the array is less than the element that is lower the two values will be exchanged.

#include <iostream>
#include <cstdlib>
 
using namespace std;
 
const int SIZE = 25;
void bubbleSort(int[], int);
 
int main()
{
	int values[SIZE];
	srand(time(NULL));
 
	for (int i = 0; i < SIZE; i++) 
		values[i] = rand() % 100;
 
	cout << "Before: ";
	for (int i = 0; i < SIZE; i++)
		cout << values[i] << " ";
	cout << endl;
 
	bubbleSort(values, SIZE);
 
	cout << "After: ";
	for (int i = 0; i < SIZE; i++)
		cout << values[i] << " ";
	cout << endl;
 
	return 0;
}
 
void bubbleSort(int v[], int size)
{
	int temp;
 
	for (int i = 0; i < size; i++) {
		for (int j = 0; j < size - 1; j++) {
			if (v[j] > v[j + 1]) {
				temp = v[j];
				v[j] = v[j + 1];
				v[j + 1] = temp;
			}
		}
	}
}
brad@dhcp-181:/media/50E9-B47B/Notepad++/Programs$ ./bubbleSort
Before: 78 97 77 0 48 64 94 77 31 86 22 2 68 2 42 42 21 35 13 36 46 98 16 12 22 
After: 0 2 2 12 13 16 21 22 22 31 35 36 42 42 46 48 64 68 77 77 78 86 94 97 98 

Insertion Sort

An insertion sort goes through a list one element at a time placing each element into its correct place. Each time an element is selected, its moved into down the list until it finds an element that it is greater than. If the current element is already greater than its previous element it moves to the next element in the list.

#include <iostream>
#include <cstdlib>
using namespace std;
 
void insertionSort(int[], int, int);
int *makeList(const int);
 
const int SIZE = 25;
 
int main()
{
	int *values;
 
	values = makeList(SIZE);
 
	cout << "Before: ";
	for (int i = 0; i < SIZE; i++)
		cout << values[i] << " ";
	cout << endl;
 
	for (int i = 0; i < SIZE; i++)
		insertionSort(values, i, values[i]);
 
	cout << "After: ";
	for (int i = 0; i < SIZE; i++)
		cout << values[i] << " ";
	cout << endl;
 
	delete[] values;
 
	return 0;
}
 
int *makeList(const int size)
{
	int *values = new int[size];
 
	srand(time(NULL));
 
	for (int i = 0; i < SIZE; i++)
		values[i] = rand() % 100;
 
	return values;
}
 
 
void insertionSort(int a[], int pos, int val)
{
	int i = pos - 1;
 
	while (i >= 0 && a[i] > val) {
		a[i + 1] = a[i];
		i--;
	}
	a[i + 1] = val;
}
brad@Lucid-Lynx:/media/50E9-B47B/Notepad++/Programs$ ./insertSort
Before: 61 49 66 97 46 16 29 23 42 76 8 52 65 7 29 3 96 64 93 28 25 55 47 90 50 
After: 3 7 8 16 23 25 28 29 29 42 46 47 49 50 52 55 61 64 65 66 76 90 93 96 97 

Quick Sort

Right now I'm not entirely sure how this functions. I'm not very good with recursive logic. I'll keep pecking at it later and see if I can get any further with it.

#include <iostream>
#include <cstdlib>
using namespace std;
 
int *makeList(const int);
void quickSort(int[], int, int);
 
const int SIZE = 25;
 
int main()
{
	int *values;
 
	values = makeList(SIZE);
 
	cout << "Before: ";
	for (int i = 0; i < SIZE; i++)
		cout << values[i] << " ";
	cout << endl;
 
	quickSort(values, 0, SIZE);
 
	cout << "After: ";
	for (int i = 0; i < SIZE; i++)
		cout << values[i] << " ";
	cout << endl;
 
 
 
	delete[] values;	
 
	return 0;
}
 
int *makeList(const int size)
{
	int *values = new int[size];
 
	srand(time(NULL));
 
	for (int i = 0; i < SIZE; i++)
		values[i] = rand() % 100;
 
	return values;
}
 
void quickSort(int v[], int left, int right)
{
	int mid, temp, i, j;
 
	i = left;
	j = right;
	mid = v[(left + right) / 2];
 
	do {
		while (v[i] < mid)
			i++;
		while (mid < v[j])
			j--;
		if (i <= j) {
			temp = v[i];
			v[i] = v[j];
			v[j] = temp;
			i++;
			j--;
		}
	} while (i <= j);
	if (left < j) quickSort(v, left, j);
	if (i < right) quickSort(v, i, right);
}
brad@Lucid-Lynx:/media/50E9-B47B/Notepad++/Programs$ ./quickSort
Before: 49 75 62 19 11 12 70 96 16 95 2 83 99 30 69 73 80 20 41 84 28 86 82 23 54 
After: 2 11 12 16 19 20 23 28 30 41 49 54 62 69 70 73 75 80 82 83 84 86 95 96 99 
#include <iostream>
#include <cstdlib>
using namespace std;
 
int *makeList(const int);
int binarySearch(int[], int, const int);
 
const int SIZE = 10;
 
int main()
{
	int grandpaSeth[] = {0, 2, 4, 7, 12, 15, 16, 19, 21, 22}, nilbog;
 
 
	cout << "Enter a value to search for: ";
	cin >> 	nilbog;
 
	if (binarySearch(grandpaSeth, nilbog, SIZE) == 0)
		cout << "Found " << nilbog << endl;
	else {
		cout << "Could not find " << nilbog << endl;	
 
		for (int i = 0; i < SIZE; i++)
			cout << grandpaSeth[i] << " ";
		cout << endl;
 
		cout << "Try again...";
		cin >> nilbog;
 
		if (binarySearch(grandpaSeth, nilbog, SIZE) == 0)
			cout << "Found " << nilbog << endl;
		else
			cout << "Could not find " << nilbog << endl;
	}				
 
	return 0;
}
 
int binarySearch(int v[], int val, const int size)
{
	int low = 0, high = size -1, i;
 
	while (low <= high) {
		i = (low + high) / 2;
		if (val == v[i])
			return 0;
		else if (val < v[i])
			high = i - 1;
		else
			low = i + 1;
	}
 
	return -1;
}
brad@Lucid-Lynx:/media/50E9-B47B/Notepad++/Programs$ ./binSearch
Enter a value to search for: 10
Could not find 10
0 2 4 7 12 15 16 19 21 22 
Try again...12
Found 12

A binary search algorithm splits a sorted list down the middle and the checks to see what side of the list the desired value exists if it does exist within the list. Then, depending on what side of the list we're searching through, we keep moving through the list until we find a value that is either equal to, greater than, or less than the desired value. In the example above, 10 was the first value searched and was first compared to see if it was equal to 15. Since they're not equal we check if to see if 10 less than or greater than 15. Less than, so we check the first half of the list. We keep going down the list until we find the number in the list or we find a value that is less than the desired value.

Binary Search Tree

A binary search tree is a programming data structure much like a linked list. Each node in the list has at most two child nodes, a right in left. The left child node contains values that are less than that of the current node while the right child node contains only values that are greater than the current node.

Insertion

When inserting into a binary tree everything is relative to the current node. Lets say that we have a binary tree that contains the values 4, 2, 9, and 7. All of which we were inserted in that order. The tree would look as follows

       [4]
       / \
      /   \
    [2]   [9]
         /
        /
      [7]

Now lets say that we wish to insert 8 into the tree. We would first compare 8 with 4. Since 8 > 4 we move to the right branch. We than compare 8 with 9. Since 8 < 9 we move to the left branch. Finally we compare 8 with 7. Since 8 > 7 we move to the right branch and also since we have now encountered NULL, the end of the list we can insert the value here.

Removal

When removing a leaf from a binary tree there are 3 possible scenarios.

  • Node with no children
  • Node with one child
  • Node with two children

If the node has no children, you can simply remove it from the tree. If a node has one child we remove the node from tree and then replace that node with its child. If the node has two children, we can shift the value of the right child node to the position of the node that we wish to remove.

Searching

Inserting and searching a binary tree are very similar. In order to insert into a tree we must first search the tree for the right spot to insert the value. However, in the case of simply searching through a tree if we reach NULL, the end of the tree, than the value we're searching for does not exist within the tree.

Traversal

I'm not quite sure that I understand how to retrieve the values stored in a tree. Here is some material on the subject. http://en.wikipedia.org/wiki/Binary_search_tree#Traversal

http://encrypt3d.wordpress.com/2010/09/21/binary-search-tree-traversal/

sysprog Topics

Processes and Programs

A program is simply a list of executable machine instructions. The idea of a process is a bit different. A process is a program during execution, the memory space allocated for the program.

Threads

Threads can be thought of as multiple programs using the same source code but executing at different points within the source code. The processor will jump back and forth between the different threads quickly enough to that to the user it appears that the execution is all taking place simultaneously.

Parent/Child Processes

In UNIX, processes are created in a tree like manner. All processes are created by a parent process, in most cases bash using the fork system call. Any process created by fork is a child process of the process that called fork. the calling process is referred to as a parent process. Both parent and child have their own individual PIDs allowing the user to distinguish the two.

#include <unistd.h>                                                             
#include <stdio.h>
 
int main()
{
    int myPid;
 
    myPid = getpid();
 
    fork();
 
    if (myPid == getpid())
        printf("I'm the parent my PID is %d\n", myPid);
    else
        printf("I'm the child my PID is %d\n", getpid());
 
    return 0;
}
brad@brad-desktop:~/c_programs$ ./forkin
I'm the parent my PID is 16251
I'm the child my PID is 16252

Shell Variables and the Environment

Shell variables are a set of variables that are used by the shell that can be manipulated by running processes. They can be used to specify file paths, the current shell version, the current user's name and many other things. For example, the bash shell has a shell variable for random numbers.

brad@brad-desktop:~/c_programs$ echo $RANDOM
21946
brad@brad-desktop:~/c_programs$ echo $USER
brad
brad@brad-desktop:~/c_programs$ echo $BASH_VERSION
4.1.5(1)-release

I/O Redirection

I/O can be redirected in several ways. One of the most common ways is to redirect output from the standard output device to some file. For instance, if a process outputs some information we don't really care about it can be redirected to /dev/null. Also, if we wish to save some output, we could redirect it to a file and use it later.

brad@brad-desktop:~/closet$ ./msg "Howdy, Stranger" >> msg.txt
brad@brad-desktop:~/closet$ ls
msg  msg.cc  msg.txt
brad@brad-desktop:~/closet$ cat msg.txt
Howdy, Stranger
brad@brad-desktop:~/closet$ 

Pipes

In UNIX, a pipe is a means to send the output from one process as input to another process.

bh011695@lab46:~/src/music/brad_notes$ cat MidiServer.java | wc -c
493

In the above command line, we cat MidiServer.java and send that output to wc which counts every character in MidiServer.java.

Servers and Sockets

A server is a process which has some information that another process would like to retrieve. A socket is the means for retrieving this information. A socket is similar to a pipe, however, a socket is capable of bidirectional communication whereas a pipe can only send data in one direction. If the server process wishes to get information from the process that is communicating with it, it may do so.

Client/Server Model

The client/server model is a means of depicting the relationship between client and server processes. The server portion of the process provides some kind of data or service to the client(s). Examples of server applications are web servers and ftp servers. Client applications are things like web browsers and e-mail clients.

Client/Server Model

Coroutines

Coroutines both continue to run, however, control will pass from one process to the other to handle some phase of the task. If you have process1 and process2 such that they are coroutines, two pipes would be required for bidirectional communication. Data would be passed from process1 to process2. Process2 would manipulate the data and send the result back to process1 to perform other manipulations on and print the result.

Zombie

A zombie process is a process that has finished executing but still has an entry remaining in the process table. Essentially it's a process that has not sent an exit status signaling completion to its parent. To remove a zombie, one can send the SIG_CHILD signal to the parent or attempt to kill the process. However, if this does not work it is possible to kill the parent at which point init will adopt the zombie and reap it itself.

Server Sockets

A server socket waits for a request to come from some client application. It will perform some manipulation on the data from the request and then can return a result to the client application.

/*
 * Sample code block
 */
#include <stdio.h>
 
int main()
{
    return(0);
}

Client Socket

A client socket is the end of a bidirectional communication with the purpose of retrieving some data from a server socket.

data Objective

Objective

Compare alternative implementations of data structures with respect to performance

Method

Time the execution to push and pop 10000 values to an array based stack and a linked list based stack.

Measurement

brad@Lucid-Lynx:/media/50E9-B47B/Notepad++/Programs$ time ./aStack

real	0m1.230s
user	0m0.716s
sys	0m0.000s
brad@Lucid-Lynx:/media/50E9-B47B/Notepad++/Programs/LinkedList$ time ./lStack

real	0m0.003s
user	0m0.004s
sys	0m0.000s

Analysis

It seems that the array stack took longer to execute than the linked list based stack. This is not at all what I was expecting. I was assuming the opposite would happen. Furthermore, the time gap is off by more than a second. This makes me wonder whether or not I've done something wrong but I ran the linked list through gdb just to make sure it wasn't skipping the pop loop but that's not the case. Perhaps my array stack algorithms for pushing and popping aren't very efficient?

sysprog Objective

Objective

design programs that handle signals - write a program that can perform an action based on a single that it receives.

Method

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
 
int main(int argc, char **argv)
{
	void wakeUp(int);
	int i;
 
	if (argc != 2) {
		exit(0);
	} else {
		i = atoi(argv[1]);
		printf("Sleeping for %d seconds.\n", i);
		signal(SIGALRM, wakeUp);
		alarm(i);
		pause();
	}
 
	return 0;
}
 
void wakeUp(int i)
{
		printf("Waking up...\n");
}

Measurement

brad@Lucid-Lynx:/media/50E9-B47B/Notepad++/Programs$ ./alarm 3
Sleeping for 3 seconds.
Waking up...

Analysis

Reflect upon your results of the measurement to ascertain your achievement of the particular course objective.

  • How did you do? - I peformed the use of the alarm signal.
  • Room for improvement? - Yes, I could add more signals such as SIGINT, SIGCHILD, etc.
  • Could the measurement process be enhanced to be more effective? Yes. Above.
  • Do you think this enhancement would be efficient to employ? Yes.
  • Could the course objective be altered to be more applicable? How would you alter it? No, it wants a demonstration of signals which you don't want to be too specific about. Give the user means to import as many signals as they wish.

Experiments

Experiment 1

Question

What will be the output of the following code?

#include <stdio.h>
int main()
{
  char name1[] = "Egon";
  char name2[] = "Peter";
  int myPid = getPid();
 
  fork();
 
  if (getpid() == myPid)
    printf("Hello, %s\n", name1);
   else
     printf("Hello, %s\n", name2);
  return 0;
}

Resources

forkdemo1.c in the Understanding Unix/Linux programming book.

Hypothesis

The output of the above program wasn't quite what I was expecting. It seems that if you differentiate between the PID of the child and parent process, you can then use that value as a means of flow control in your program to split the load of the program between the parent and child processes.

Experiment

I'm going to compile the code from above and check the output. In the previous example of fork, certain code was executed twice. In this case, it should split the execution of the print statements. It should give the output of each line only once, unlike what happened in forkdemo1.c.

Data

brad@dhcp-181:/media/50E9-B47B/Notepad++/Programs$ gcc -o forkExp forkExp.c
brad@dhcp-181:/media/50E9-B47B/Notepad++/Programs$ ./forkExp
Hello, Egon
Hello, Peter

Analysis

My hypothesis was correct. Based on the PID, it is possible to use this as a means of flow control giving the user the possibility to pass certain tasks over to the child process.

Conclusions

It would be very interesting to investigate this further. Something that would be fairly interesting, a possible other experiment, would be to take a program which contains two functions that would be doing some fairly long calculations but neither function relies on the other. Using the method above split the two functions between the parent and child and clock the execution time and compare this with the execution time of calling the functions sequentially.

Experiment 2

Question

If you had multiple unrelated manipulations to perform, could you fork and use this to cut the overall execution time in half?

Resources

Collect information and resources (such as URLs of web resources), and comment on knowledge obtained that you think will provide useful background information to aid in performing the experiment.

Hypothesis

Yes, this should speed up execution time as you have two processes running simultaneously instead of performing one set of manipulations followed by the next set.

Experiment

I will create two programs, one that performs the manipulations sequentially and another that uses fork to perform the operations.

Data

#include <limits.h>
#include <unistd.h>
 
int main()
{
	unsigned long l, k;
 
	for (l = 0; l < ULONG_MAX; l++)
		;
 
	for (k = 0; k < ULONG_MAX; k++)
		;
 
	return 0;
}
#include <limits.h>
#include <unistd.h>
 
int main()
{
	unsigned long l;
	unsigned long k;
	int myPid;
 
	myPid = getpid();
 
	fork();
 
	if (myPid == getpid()) {
		for (l = 0; l < ULONG_MAX; l++)
			;
	} else {
		for (k = 0; k < ULONG_MAX; k++)
			;
	}
 
	return 0;
}
brad@Lucid-Lynx:~/c_programs$ time ./seqUlong

real	0m17.570s
user	0m17.513s
sys	0m0.000s
brad@Lucid-Lynx:~/c_programs$ time ./forUlong

real	0m12.831s
user	0m12.029s
sys	0m0.032s

Analysis

Based on the data collected:

  • was your hypothesis correct? - Yes
  • was your hypothesis not applicable? - Yes
  • is there more going on than you originally thought? (shortcomings in hypothesis) - I'm sure there's something going on that I'm not aware of
  • what shortcomings might there be in your experiment? - the number of child processes. I could retest with more child processes. Say 10 o4 so
  • what shortcomings might there be in your data?

Conclusions

Forking between these two processes cut 5 seconds off of the program's execution time. 5 seconds is a lot when it comes to processor time. This means that this is a very good method for cutting down on execution time.

Retest

If you're doing an experiment instead of a retest, delete this section.

If you've opted to test the experiment of someone else, delete the experiment section and steps above; perform the following steps:

State Experiment

Whose existing experiment are you going to retest? Prove the URL, note the author, and restate their question.

Resources

Evaluate their resources and commentary. Answer the following questions:

  • Do you feel the given resources are adequate in providing sufficient background information?
  • Are there additional resources you've found that you can add to the resources list?
  • Does the original experimenter appear to have obtained a necessary fundamental understanding of the concepts leading up to their stated experiment?
  • If you find a deviation in opinion, state why you think this might exist.

Hypothesis

State their experiment's hypothesis. Answer the following questions:

  • Do you feel their hypothesis is adequate in capturing the essence of what they're trying to discover?
  • What improvements could you make to their hypothesis, if any?

Experiment

Follow the steps given to recreate the original experiment. Answer the following questions:

  • Are the instructions correct in successfully achieving the results?
  • Is there room for improvement in the experiment instructions/description? What suggestions would you make?
  • Would you make any alterations to the structure of the experiment to yield better results? What, and why?

Data

Publish the data you have gained from your performing of the experiment here.

Analysis

Answer the following:

  • Does the data seem in-line with the published data from the original author?
  • Can you explain any deviations?
  • How about any sources of error?
  • Is the stated hypothesis adequate?

Conclusions

Answer the following:

  • What conclusions can you make based on performing the experiment?
  • Do you feel the experiment was adequate in obtaining a further understanding of a concept?
  • Does the original author appear to have gotten some value out of performing the experiment?
  • Any suggestions or observations that could improve this particular process (in general, or specifically you, or specifically for the original author).
opus/fall2011/bh011695/start.txt · Last modified: 2014/01/19 09:20 by 127.0.0.1