Machine Problem 1: Preliminaries

Objectives

  1. Log in to fourier (the course server) with SSH
  2. Learn to use a terminal multiplexer (tmux)
  3. Clone the public, read-only course repository from Bitbucket
  4. Obtain write access to our shared, private repository
  5. Commit changes to your local repository
  6. Push changes to your private repository
  7. Pull/Merge updates from the public repository
  8. Edit files on fourier
  9. Compile C code with gcc
  10. Use make to run tasks
  11. Understand the basics of how the make build tool works

Overview

You'll write plenty of code this semester, but before you get to it you first need to learn how to edit, test, and submit your work. We'll cover those steps here.

To keep this writeup short we'll do lots of interactive demonstrations, but the critical steps and commands will be documented for future reference.

fourier.cs.iit.edu

Ideally, you should work on the CS department Linux course server --- fourier.cs.iit.edu --- to write, test, and submit all your work. This will ensure a consistent working environment. You might get away with working on machine problems on your own computer if you have Linux and a similar toolchain to that installed on fourier, but if you run into issues you'll be on your own.

Machine problem writeups will always assume you're working on fourier. So to start: let's get logged in.

After obtaining your login information (your username is typically the same as the one you use for MyIIT, and your password is set to your CWID), you'll log in to fourier with an SSH client. At the terminal, you can do this with the command:

ssh username@fourier.cs.iit.edu

On Windows you may need to start up a separate SSH client such as PuTTY if you don't have a command line client.

After logging in, resist the overwhelming urge to immediately start being productive! Repeat after me: TERMINAL MULTIPLEXERS ARE AWESOME. You'll learn about them next.

tmux

MPs require multiple coding, testing, and debugging sessions to complete, which require multiple corresponding login sessions on fourier. Each time you login, you'll have to navigate into the appropriate work directory, re-open source file(s) in editors, start up debugging sessions, run trace files to obtain output, etc. All before you can even think about getting work done.

Ugh.

All the above constitute annoying cognitive overhead, and we can all use less of that (especially given how difficult it is for most students to allocate time to sit down and work on coursework in the first place).

Wouldn't it be great if you could set things up --- your editing windows, debuggers, trace output position --- just the way you like it, one time, then have it remain that way each time you log back in? Heck yeah. That's where terminal multiplexers fit in.

Technically, a terminal multiplexer is a program that:

  1. Allows you to create and manage multiple terminal sessions from a single screen. You can run a different program in each session (e.g., editor, debugger, shell).
  2. Continues to run after you log out, so when you reconnect you can simply pick up where you left off.

Less technically, a terminal multiplexer = bliss.

To start tmux, just use the command tmux. I'll walk you through a few demo sessions, but the important keys are as follows:

After detaching from a session, the following command will re-attach you to it (given that you only have one tmux session running):

tmux at

You should NOT create a new tmux session (with the command tmux) each time you log in. Instead, you need only do this once --- on subsequent logins you will simply reattach to your existing session with tmux at.

So... Logged in: check. tmux session attached: check. Moving on.

Git / Bitbucket

Git is a distributed version control system, and Bitbucket is a free Git repository hosting service. You'll use the first to manage all your code and the second to share it with me.

Here are the steps for setting things up --- we'll walk you through them interactively.

  1. Clone the private repository (which is owned by the instructor) --- you must've accepted an e-mail invitation from the instructor and connected the repository to either a pre-exising or new account on Bitbucket for this to work:

    git clone https://bitbucket.org/michaelee/cs351-semester-username.git
    
  2. Set your "upstream" repository to the official public course repository (also owned by the instructor's account). This makes it so you can grab updates from the official repository in the future.

    cd cs351-semester-username
    git remote add upstream https://bitbucket.org/michaelee/cs351.git
    
  3. Fetch the initial contents of the public repository, merge them into your local branch, then push them to your private repository:

    git fetch upstream
    git merge upstream/master
    git push origin master
    

Now you can make changes to files within your local repository and commit them. Add your information to the "README.md" file (you can scan ahead to the section on editors if you need some help choosing a suitable text editor), then run this command:

git commit -am "Adding information to README"

Now push your commit to the private repository on Bitbucket:

git push origin master

This previous step is effectively how you "submit" your work, as I will be pulling from this private shared repository after the submission deadline to get your changes. If you don't push, I won't see (and can't grade) your work!

That said, you don't need to push after each commit. I recommend you commit frequently while coding --- a push will send all commits you've made to the remote repository.

Finally: before starting each machine problem, you should make sure all your local changes are commited, then fetch and merge any additions to the public course repository with git fetch upstream; git merge upstream/master. We'll remind you to do this in each machine problem writeup.

On editors

A good programmer has strong opinions about programming languages, coding conventions, and text editors. There are only two UNIX text editors worth learning, and learning well: Emacs and Vim. Both are installed on fourier. Pick one and learn a new feature every day.

If you can't be bothered investing time and energy into a tool that you'll use more than any other and will save you countless keystrokes and hours, then use the nano editor instead. It's also on fourier. I don't recommend it.

Building

You'll write code in the next machine problem. Promise. This time we'll skip that part and jump to the fun parts: building and running.

For the rest of this machine problem you'll work in the "mps/01" subdirectory of your repository. In there, you'll find these files:

Let's try to compile "main.c" (the $ indicates the shell prompt, and is followed by the command you should enter):

$ gcc main.c
/tmp/ccbmZeXz.o: In function `main':
main.c:(.text+0x24): undefined reference to `say_hello_to'
main.c:(.text+0x30): undefined reference to `say_hello_to'
collect2: ld returned 1 exit status

Didn't work. (Why not?)

Try compiling "hello.c":

$ gcc hello.c
/usr/lib/gcc/x86_64-redhat-linux/4.4.7/../../../../lib64/crt1.o: In function `_start':
(.text+0x20): undefined reference to `main'
collect2: ld returned 1 exit status

What gives?

Now try:

$ gcc hello.c main.c

Good. No errors. The compiler created an executable for you named "a.out", by default. Run it:

$ ./a.out 
Hello world!
$ ./a.out Master
Hello Master!

We can of course rename the executable, but let's have the compiler put it in the right place for us:

$ rm a.out
$ gcc -o hello hello.c main.c
$ ./hello Overlord
Hello Overlord!

But what about the multi-stage compilation and linking process we discussed in class? Never fear: this is actually going on behind the scenes already (try invoking gcc with the -v flag to see what it's doing). To build the intermediate object files and link them together in separate steps, do:

$ gcc -c hello.c
$ gcc -c main.c
$ gcc -o hello hello.o main.o

See how we're referring to the ".o" files in the third step? Those are the object files that were generated when we invoked gcc with the -c flag, which tells it to stop before the linking step.

If the project being built is complex enough, it may be necessary to separate the final linking step from the creation of a multitude of intermediate object files. Manually invoking the compiler in such situations is a real pain, and definitely not something we'd want to keep doing!

Enter the standard C build tool: make

make

make is used to automate builds. Try it out (first, we delete the files we previously built):

$ rm -f *.o hello
$ make
gcc -g -Wall -O2   -c -o hello.o hello.c
gcc -g -Wall -O2   -c -o main.o main.c
gcc -g -Wall -O2 -o hello hello.o main.o

Woohoo! There's automation for you! Try it again:

$ make
make: Nothing to be done for `all'.

make knows that no files have changed since we last build the executable, see. We can update the timestamp of one of the files (using touch) to force a rebuild:

$ touch hello.c
$ make
gcc -g -Wall -O2   -c -o hello.o hello.c
gcc -g -Wall -O2 -o hello hello.o main.o

See how it only rebuilds one of the intermediate object files? Pretty nifty.

We can also ask it to run a test for us, then clean up generated files:

$ make test
Running test...
./hello tester
Hello tester!
$ make clean
rm -f hello.o main.o  hello

Makefiles

make is not magical, of course --- it makes use of the provide "Makefile" to determine what action(s) to take, depending on the specified target. For reference, here are the contents of said Makefile. Note the four targets (all, hello, test, and clean), two of which we invoked explicitly before --- the first target (all), it turns out, is used by default when we ran the make command with no argument.

CC      = gcc
CFLAGS  = -g -Wall -O2
SRCS    = hello.c main.c
OBJS    = $(SRCS:.c=.o)

all: hello

hello: $(OBJS)
    $(CC) $(CFLAGS) -o hello $(OBJS)

test: hello
    @echo "Running test..."
    ./hello tester

clean:
    rm -f $(OBJS) hello

We'll go over as much of this during our lab demo as we can, but you should check out the GNU make manual for details --- at the very least, skim through the Introduction.

Finishing up

To earn the points for this machine problem, you must have pushed the contents of the public course repository to your private one on Bitbucket. This is also a prerequisite for working on all subsequent machine problems, so please make sure you know how to do this!

And the big procedural takeaways:

  1. Before starting a machine problem, (git fetch upstream ; git merge upstream/master)
  2. Commit often, and to submit, git push origin master
  3. make to build, and often times some variation on make test to run a hardcoded test.