CSC443/CSC343 Sep21

slide version

single file version

Contents

  1. Today's Topics
  2. Unix Sixth Edition
  3. Low Level
  4. Major Abstraction: Processes
  5. Process Implementation
  6. Process Control Block Members
  7. Process Management System Calls
  8. Files
  9. Open system call
  10. Redirecting I/O
  11. Kernel Data Structures for Files
  12. Descriptor Tables
  13. System File Table
  14. In Memory inode Table
  15. Pipes
  16. Reading AND Writing with Pipes
  17. Unix (6th edition) Directories
  18. Directory Entries
  19. Opening a File
  20. File Access Protection (Unix 6th)
  21. Example
  22. Extensions
  23. Concurrency (Application Programmer's Perspective)
  24. The select System Call
  25. Select
  26. Example
  27. Thread Version

Today's Topics[1] [top]

Unix Sixth Edition[2] [top]

This early version (but not too early) of Unix is relative simple and elegant.

For its features and the state of the hardware and demands in 1970, it provided "solutions" to many operating system requirements.

A number of those "solutions" became deficient based on demands and improvements in and speed of hardware components.

It has had a major influence on modern operating systems.

Low Level[3] [top]

We have discussed role in operating systems of most of these:

Major Abstraction: Processes[4] [top]

Process abstraction:

Process Implementation[5] [top]

Operating system data structures

Process Control Block Members[6] [top]

Included

Process Management System Calls[7] [top]

Files[8] [top]

Unix File system was:

Open system call[9] [top]

    1	
    2	int main()
    3	{
    4	  int fd; /* for file descriptor */
    5	  char buffer[1024];
    6	  int count;
    7	
    8	  if ( (fd = open("/home/gal/blob", O_RDWR) == -1 ) {
    9	    /* couldn't be opened */
   10	    perror("/home/gal/blob"));
   11	    exit(1);
   12	  }
   13	  if ((count = read(fd, buffer, 1024)) == -1) {
   14	    /* the read failed */
   15	    perror("read"); /* or printf("%s\n", strerror(errno)); */
   16	    exit(1);
   17	  }
   18	  /* buffer now contains count bytes that were read from the file */
   19	  ...
   20	}

Redirecting I/O[10] [top]

File descriptors 0, 1, and 2 are in use when a process is created and correspond to standard in, standard out, and standard error respectively.

These are ordinarily keyboard input, terminal output, and terminal output.

This can be changed as in this code fragment:

    1	if (fork() == 0) {
    2	  /* set up file descriptor 1 in child process */
    3	  close(1);
    4	  if (open("/home/me/blob", O_WRONLY) == -1) {
    5	    perror("/home/me/blob");
    6	    exit(1);
    7	  } else {
    8	    execlp("/home/me/prog", "prog", "50", (char *) 0);
    9	    printf("/home/me/prog: Could not execute.\n");
   10	    exit(1);
   11	  }
   12	
   13	}
   14	 
   15	/* parent continues here */
   16	...

If the child reads from standard input (e.g. with

      
 scanf or fgets(buf, N, stdin)
    

It will read from the file '/home/me/blob'

A successful open returns the first unused file descriptor: the first (smallest) index in the descriptor table of the first unused entry.

Kernel Data Structures for Files[11] [top]

Descriptor Tables[12] [top]

File descriptors are just subscripts for this table.

Entries are either NULL or else a pointer into the System File table.

System File Table[13] [top]

An entry in the system file table is made for each file that is opened by some current process.

An entry contains

The reference count will be 2 if a parent has an open file and forks a child.

But if two unrelated processes open the same file, there will be two separate entries and the reference count in each will be 1.

In Memory inode Table [14] [top]

For each file on disk, there is an inode for that file (also stored on the disk) is a struct containing file information including

When a file is opened its inode is read into an available slot in the inode table in memory. The in memory inode table contains a few more members including

Even if more than one process has a file open, there will only be one entry in the inode table for that file, but the reference count will reflect the number of uses of that inode.

Pipes[15] [top]

Pipes provide a convenient communication between a parent process and a child process.

The file system provides an implementation of pipes/

Redirection ccould be combined with pipes so that ordinary input/output uses the pipes.

A simple pipe example (no redirection):

    1	
    2	/**
    3	/* p[0] and p[1] will hold file descriptors for the pipe 
    4	 * p[0] descriptor is for reading
    5	 * p[1] descriptor is for writing
    6	 */
    7	int p[2]; 
    8	pipe(p);  /* create a pipe (assume no errors) */
    9	
   10	if ( fork() == 0 ) {
   11	  char buf[80];
   12	  close(p[1]);  /* Child will only read from, not write to the pipe */
   13	  while( read(p[0], buf, 80) > 0 ) {
   14	    /* use data from the parent */
   15	  }
   16	  exit(0);
   17	} else {
   18	  char buf[80];
   19	  close(p[0]);  /* Parent will only write to, not read from the pipe */
   20	  int more_data_to_write = 1;
   21	  while(more_data_to_write) {
   22	    /* generate data in buf */
   23	    ...
   24	    write(p[1], buf, 80);
   25	    more_data_to_write = moreData();
   26	  }
   27	}

Reading AND Writing with Pipes[16] [top]

A pipe provides only one-way communication.

If parent and/or child will read AND write, then 2 pipes are needed.

If a process writes to the 'write' end of a pipe and then reads from the 'read' end of the same pipe, it could read its own data.

Unix (6th edition) Directories[17] [top]

Directories were themselves just very simple files organized like this example:

/home/gal
. 120
.. 18
docs 132
projects 128
.bash_profile 126
hello.c 160
/home/gal/docs
. 132
.. 120
assign1.txt 180
hello.c 160

Directory Entries[18] [top]

Each entry in a Unix 6th directory was a fixed size: 16 bytes

The link system call permits creating a file reference in one directory to an existing file in another directory. The names don't have to be the same, but they refer to the same file contents.

An example above is the file with inode number 160: hello.c

The referenced inode contains all the file information (permissions, file size, last time of modification, location of blocks on disk, etc.)

The inode also contains a link count.

Either file hello.c could now be 'deleted', but that would

Opening a File[19] [top]

Suppose we call 'open' to open the file

      "/home/gal/docs/hello.c"
    

How many inodes must be consulted?

Ugh! Possibly lots of disk accesses!

File Access Protection (Unix 6th)[20] [top]

The familiar protection is based on the smallest class to which the executing process belongs: owner, group, other.

Smaller means: owner is 'smaller' than group, which is 'smaller' than other.

Example[21] [top]

Suppose the current directory (".") contains two subdirectories A and B

Suppose users tom and sue are members of the adm group, but charles is not.

bash-3.2$ ls -lR
.:
total 8
drwxr-xr-x 2 tom adm 4096 Jul 21 16:49 A/
drwxr----- 2 tom adm 4096 Jul 21 16:50 B/

./A:
total 4
-rw-rw-rw- 1 tom adm  984 Jul 21 16:49 x

./B:
total 8
-r--rw-rw- 1 tom adm  302 Jul 21 16:50 x
-rw----rw- 1 sue adm  445 Jul 21 16:50 y
    

May charles list the contents of directory A?
May chalres read A/x?
May sue list the contents of directory B?
May sue modify B/y?
May tom modify B/x?
May tom modify B/y?

Extensions[22] [top]

Concurrency (Application Programmer's Perspective)[23] [top]

Some facilities for concurrency are different in an OS than for the application programmer, but essential concepts are the same.

The select System Call[24] [top]

Here is an example to get started:

    1	
    2	int main(int argc, char * argv[]) {
    3	  fd_set rfds;
    4	  struct timeval tv;
    5	  int seconds = 5;
    6	  int retval;
    7	  const int MAXBUF = 120;
    8	  char buf[MAXBUF];
    9	  char magic[] = "select";
   10	
   11	
   12	  /* Get timeout seconds */
   13	  if ( argc > 1 ) {
   14	    seconds = atoi(argv[1]);
   15	    if ( seconds == 0 ) {
   16	      seconds = 5;
   17	    }
   18	  }
   19	  /* Watch stdin (fd 0) to see when it has input. */
   20	  FD_ZERO(&rfds);
   21	  FD_SET(0, &rfds);
   22	
   23	  /* Wait up to ? seconds. */
   24	  tv.tv_sec = seconds;
   25	  tv.tv_usec = 0;
   26	
   27	  setbuf(stdout, NULL);
   28	  printf("You have %d seconds to enter the magic word.\n", seconds);
   29	  printf("> ");
   30	  retval = select(1, &rfds, NULL, NULL, &tv);
   31	  /* Donb
   32	t rely on the value of tv now! */
   33	
   34	
   35	  if (retval == -1)
   36	    perror("select()");
   37	  else if (retval) {
   38	    fgets(buf, MAXBUF, stdin);
   39	    buf[strlen(buf) - 1] = 0;
   40	    if (!strcmp(buf, magic)) {
   41	      printf("Correct!\n");
   42	    } else {
   43	      printf("Sorry, '%s' is not the magic word.\n", buf);
   44	    }
   45	  }
   46	
   47	  /* FD_ISSET(0, &rfds) will be true. */
   48	  else {
   49	    printf("\nSorry, %d seconds expired with no guess entered.\n",
   50	  seconds);
   51	  }
   52	
   53	  return 0;
   54	}

Select[25] [top]

The select system call lets you wait for a set of file descriptors to be ready to use without causing blocking that would occur if you chose one (say to read from) and it was the wrong one (no input yet).

The rlogind program is/was excuted to permit remote logins from a Unix client machine to a Unix server machine.

It gets input from a descriptor for the remote client, writes to a descriptor for a pseudo terminal.

An application running on the server thinks its input is coming from the pseudo terminal and so sends its output there.

The rlogind daemon has to also read the descriptor for the pseudo terminal and write a descriptor to send the output to the client.

Which of these 4 operations should it try first?

The select system call lets it repeatedly chose one that will not block.

Example[26] [top]

    1	
    2	void rlogind(int r_in, int r_out, int l_in, int l_out) 
    3	{
    4	  fd_set in = 0, out;
    5	  int want_l_write = 0, want_r_write = 0;
    6	  int want_l_read = 1, want_r_read = 1;
    7	  int eof = 0, tsize, fsize, wret;
    8	  char fubf[BSIZE], tbuf[BSIZE];
    9	
   10	  fcntl(r_in, F_SETFL, O_NONBLOCK);
   11	  fcntl(r_out, F_SETFL, O_NONBLOCK);
   12	  fcntl(l_in, F_SETFL, O_NONBLOCK);
   13	  fcntl(l_out, F_SETFL, O_NONBLOCK);
   14	
   15	  while(!eof) {
   16	    FD_ZERO(&in);
   17	    FD_ZERO(&out);
   18	    if(want_l_read) 
   19	      FD_SET(l_in, &in);
   20	    if(want_r_read) 
   21	      FD_SET(r_in, &in);
   22	    if(want_l_write) 
   23	      FD_SET(l_out, &in);
   24	    if(want_r_write) 
   25	      FD_SET(r_out, &in);
   26	
   27	    select(MAXFD, &in, &out, 0, 0);
   28	    if (FD_ISSET(l_in, &in)) {
   29	      if ((tsize=read(l_in, tbuf, BSIZE)) > 0 ) {
   30		want_l_read = 0;
   31		want_r_write = 1;
   32	      } else 
   33		eof = 1;
   34	    }
   35	    if (FD_ISSET(r_in, &in)) {
   36	      if ((tsize=read(l_in, tbuf, BSIZE)) > 0 ) {
   37		want_r_read = 0;
   38		want_l_write = 1;
   39	      } else 
   40		eof = 1;
   41	    }
   42	    ...
    	  }
   	}

Thread Version[27] [top]


void incoming(int r_in, int l_out)
{
    int eof = 0;
    char buf[BSIZE];
    int size;

    while(!eof) {
      size = read(r_in, buf, BSIZE);
      if (size <= 0) 
        eof = 1;
      if (write(l_out, buf, size) <= 0)
	eof = 1;
    }
}

void outgoing(int l_in, int r_out)
{
    int eof = 0;
    char buf[BSIZE];
    int size;

    while(!eof) {
      size = read(l_in, buf, BSIZE);
      if (size <= 0) 
        eof = 1;
      if (write(r_out, buf, size) <= 0)
	eof = 1;
    }
}