Operating Systems 2017F Lecture 5: Difference between revisions
(2 intermediate revisions by the same user not shown) | |||
Line 51: | Line 51: | ||
*Getopt: a better way to find environmental variable | *Getopt: a better way to find environmental variable | ||
*'''How many are there?''' | |||
**arg c tells us how many are in argv[] not envp[] | |||
**we can look at all of the environment variables using env in the shell -- There are a lot! | |||
**Accessing argv[] and envp[] from a c programming can be accomplished using the unistd.h library | |||
***getopt for manipulating argv[] | |||
***getenv for manipulating envp[] | |||
====Signals==== | ====Signals==== | ||
Line 58: | Line 63: | ||
**Sigkill, sigterm (ctrl+c), sigstop (ctrl+z), sigpipe (when | died), sighup (when close terminal window) | **Sigkill, sigterm (ctrl+c), sigstop (ctrl+z), sigpipe (when | died), sighup (when close terminal window) | ||
**Sigchild(when a child process terminated), | **Sigchild(when a child process terminated), | ||
*Signals allow us to communicate with other processes through the kernel. The signal is a way to alert other processes that some event has happened giving the process some time to react. | |||
**for example the kill command sends a signal that can suspend or terminate a running process | |||
***kill -STOP pid or kill - CONT pid | |||
**use man 7 signal to see the list of available signals | |||
***SIGHUP: tells the process that the modem has just hung-up | |||
***SIGCHLD: is a signal that gets sent when a child process terminates | |||
***SIGUSR1: is a user defined signal | |||
*sigaction() is a programming tool that allows us to interact with signals | |||
**It is a structure that contains function pointers -- we have to define these functions in our code | |||
*Implementing a shell | |||
**What is the best strategy for reading and understanding code? | |||
***Start with main or where execution starts. Most often we won't have a main, just a large body of code. | |||
***temporarily ignore variable definitions until they are required | |||
***Notice the different kinds of control structures and conditionals and start reviewing the code here. Examine the code line by line. | |||
*Example of a shell program. What happens in the code? | |||
<syntaxhighlight lang="c"> | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <unistd.h> | |||
#include <string.h> | |||
#include <errno.h> | |||
#include <sys/types.h> | |||
#define BUFFER_SIZE 1<<16 | |||
#define ARR_SIZE 1<<16 | |||
void parse_args(char *buffer, char** args, | |||
size_t args_size, size_t *nargs) | |||
{ | |||
char *buf_args[args_size]; /* You need C99 */ | |||
char **cp; | |||
char *wbuf; | |||
size_t i, j; | |||
wbuf=buffer; | |||
buf_args[0]=buffer; | |||
args[0] =buffer; | |||
for(cp=buf_args; (*cp=strsep(&wbuf, " \n\t")) != NULL ;){ | |||
if ((*cp != '\0') && (++cp >= &buf_args[args_size])) | |||
break; | |||
} | |||
for (j=i=0; buf_args[i]!=NULL; i++){ | |||
if(strlen(buf_args[i])>0) | |||
args[j++]=buf_args[i]; | |||
} | |||
*nargs=j; | |||
args[j]=NULL; | |||
} | |||
int main(int argc, char *argv[], char *envp[]){ | |||
char buffer[BUFFER_SIZE]; | |||
char *args[ARR_SIZE]; | |||
int *ret_status; | |||
size_t nargs; | |||
pid_t pid; | |||
while(1){ | |||
printf("$ "); | |||
fgets(buffer, BUFFER_SIZE, stdin); | |||
parse_args(buffer, args, ARR_SIZE, &nargs); | |||
if (nargs==0) continue; | |||
if (!strcmp(args[0], "exit" )) exit(0); | |||
pid = fork(); | |||
if (pid){ | |||
printf("Waiting for child (%d)\n", pid); | |||
pid = wait(ret_status); | |||
printf("Child (%d) finished\n", pid); | |||
} else { | |||
if( execvp(args[0], args)) { | |||
puts(strerror(errno)); | |||
exit(127); | |||
} | |||
} | |||
} | |||
return 0; | |||
} | |||
</syntaxhighlight> | |||
*Start in main at the infinite while loop | |||
**printf prints the shell prompt | |||
**parse-args takes the command line arguments it receives from fgets and places these values in the args[] array. it also returns the total number of arguments received via nargs. At this point understanding all of the details as to how this function works is not necessary. To get the full picture we would have to examine the code's fine details which takes time. | |||
*After the aguments have been parsed, check to see if there are arguments to work with and is the argument exit. If no arguments are entered then jump to the start of the loop; if the argument is 'exit' then quit the program. | |||
**The conditional is where all of the work gets done. Any argument other than 'none' or 'exit' cause a fork(). A child process is cloned and the parent is made to wait until the child process finishes. The execvp runs the program requested using the entered argument. An example would be 'ls'; in this case the child process would run the ls program. | |||
***If the argument is not understood then an error results and is printed stdout and the child process terminates. Note that the error string is printed from errno. | |||
*1 << 16 is interesting. it is a left shift operator that computes 65536. The most significant bit in a 16 bit number is set to 1. It's the same as saying 2^16. Defining this statement in a preprocessor definition means that it runs prior to compilation possibly saving some time. |
Latest revision as of 00:44, 5 October 2017
Video
Video from the lecture given on September 21, 2017 is now available.
Code
Code and files from the lecture (captured as they were at the end) are available here.
Notes
IO Redirection
- The > operator output to a location
- Eg. Ls > foo.txt
- The | operator connect the output of the 1st program to the input of the 2nd program
- Eg. Shuf foo.txt | shuf > shuf2.txt (pipe operater connect output of shuf foo.txt to input of shuf then output to shuf2.txt)
- File usually comes with standard input (0), standard output(1),standard error(error message, port 2). This defined when program started
- Ls foo > test.txt 2 >error.txt: redirect error message (port 2) of foo to error.txt
- Ls foo>& all.txt: redirect everything to output file
- Bc -l: This command bring up the calculater
- The convention is 3 file descriptors are available once the system boots. They can be accessed by a shell and used to send data in both directions.
- file descriptor 0 is stdin
- file descriptor 1 is stdout
- file descriptor 2 is stderr
- redirections is accomplished using the > (redirect to) or < (redirect from)
- example: ls > foo.txt creates a file called foo.txt and puts the result of ls in it
- example: ls foo > test.txt 2> errors.txt will generate errors because ls won't be able to find foo. In this case nothing will be put into test.txt instead the resulting error will be sent to errors.txt. 2> is the convention for selecting file descriptor 2.
- using >& will merge stdout and stderr
- The pipe | operator
- This operator allows us to join output from one process sending the data to another process.
- example: seq 1 100 num.txt | shuf > shuf2.txt
- The result is the sequence of numbers from 1 to 100 are put in num.txt then sent to shuf to be "shuffled" and finally redirected to shuf2.txt
Manipulating environment variables
int main(int argc, char *argv[], char *envp[]){
While (envp[i] != NULL) {
If (strcmp(“User=“, envp[i], 5){
printf(“%s\n”, envp[i]);
char *username = envp[i] +5; //pointer arithmetic
}
I++;
}
printf (“%s\n”, username);
return 0;
}
- Getopt: a better way to find environmental variable
- How many are there?
- arg c tells us how many are in argv[] not envp[]
- we can look at all of the environment variables using env in the shell -- There are a lot!
- Accessing argv[] and envp[] from a c programming can be accomplished using the unistd.h library
- getopt for manipulating argv[]
- getenv for manipulating envp[]
Signals
- Kill: send a signal to destroy a program
- Kill -stop 5617(This stop a progam) and Kill -cont 5617(resume a program)
- Sigkill, sigterm (ctrl+c), sigstop (ctrl+z), sigpipe (when | died), sighup (when close terminal window)
- Sigchild(when a child process terminated),
- Signals allow us to communicate with other processes through the kernel. The signal is a way to alert other processes that some event has happened giving the process some time to react.
- for example the kill command sends a signal that can suspend or terminate a running process
- kill -STOP pid or kill - CONT pid
- use man 7 signal to see the list of available signals
- SIGHUP: tells the process that the modem has just hung-up
- SIGCHLD: is a signal that gets sent when a child process terminates
- SIGUSR1: is a user defined signal
- for example the kill command sends a signal that can suspend or terminate a running process
- sigaction() is a programming tool that allows us to interact with signals
- It is a structure that contains function pointers -- we have to define these functions in our code
- Implementing a shell
- What is the best strategy for reading and understanding code?
- Start with main or where execution starts. Most often we won't have a main, just a large body of code.
- temporarily ignore variable definitions until they are required
- Notice the different kinds of control structures and conditionals and start reviewing the code here. Examine the code line by line.
- What is the best strategy for reading and understanding code?
- Example of a shell program. What happens in the code?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#define BUFFER_SIZE 1<<16
#define ARR_SIZE 1<<16
void parse_args(char *buffer, char** args,
size_t args_size, size_t *nargs)
{
char *buf_args[args_size]; /* You need C99 */
char **cp;
char *wbuf;
size_t i, j;
wbuf=buffer;
buf_args[0]=buffer;
args[0] =buffer;
for(cp=buf_args; (*cp=strsep(&wbuf, " \n\t")) != NULL ;){
if ((*cp != '\0') && (++cp >= &buf_args[args_size]))
break;
}
for (j=i=0; buf_args[i]!=NULL; i++){
if(strlen(buf_args[i])>0)
args[j++]=buf_args[i];
}
*nargs=j;
args[j]=NULL;
}
int main(int argc, char *argv[], char *envp[]){
char buffer[BUFFER_SIZE];
char *args[ARR_SIZE];
int *ret_status;
size_t nargs;
pid_t pid;
while(1){
printf("$ ");
fgets(buffer, BUFFER_SIZE, stdin);
parse_args(buffer, args, ARR_SIZE, &nargs);
if (nargs==0) continue;
if (!strcmp(args[0], "exit" )) exit(0);
pid = fork();
if (pid){
printf("Waiting for child (%d)\n", pid);
pid = wait(ret_status);
printf("Child (%d) finished\n", pid);
} else {
if( execvp(args[0], args)) {
puts(strerror(errno));
exit(127);
}
}
}
return 0;
}
- Start in main at the infinite while loop
- printf prints the shell prompt
- parse-args takes the command line arguments it receives from fgets and places these values in the args[] array. it also returns the total number of arguments received via nargs. At this point understanding all of the details as to how this function works is not necessary. To get the full picture we would have to examine the code's fine details which takes time.
- After the aguments have been parsed, check to see if there are arguments to work with and is the argument exit. If no arguments are entered then jump to the start of the loop; if the argument is 'exit' then quit the program.
- The conditional is where all of the work gets done. Any argument other than 'none' or 'exit' cause a fork(). A child process is cloned and the parent is made to wait until the child process finishes. The execvp runs the program requested using the entered argument. An example would be 'ls'; in this case the child process would run the ls program.
- If the argument is not understood then an error results and is printed stdout and the child process terminates. Note that the error string is printed from errno.
- The conditional is where all of the work gets done. Any argument other than 'none' or 'exit' cause a fork(). A child process is cloned and the parent is made to wait until the child process finishes. The execvp runs the program requested using the entered argument. An example would be 'ls'; in this case the child process would run the ls program.
- 1 << 16 is interesting. it is a left shift operator that computes 65536. The most significant bit in a 16 bit number is set to 1. It's the same as saying 2^16. Defining this statement in a preprocessor definition means that it runs prior to compilation possibly saving some time.