Written by Luka Kerr on June 20, 2018

## Bit Manipulation

### Bitwise Operators

• &
• Bitwise AND
• 10101010 & 11110000 = 10100000
• |
• Bitwise OR
• 10101010 | 11110000 = 11111010
• ^
• Bitwise XOR
• 10101010 ^ 11110000 = 01011010
• ~
• Bitwise NEG (flips the bits)
• ~10101010 = 01010101
• <<
• Left shift
• 11110010 << 2 = 11001000
• >>
• Right shift
• 11110010 >> 2 = 00111100

## Memory

### Regions

• Code
• Contains machine code for a program
• Once initialised, doesn’t change
• Data
• Contains global variables and constants
• Once initialised, doesnt change
• Heap
• Contains dynamicaly allocated data created by malloc, calloc, realloc
• Is able to increase (malloc()) and decrease (free()) throughout the program lifecycle
• Stack
• Contains local variables that get destroyed once they go out of scope
• Each function has a stack frame created when the function is called, and destroyed when the function returns

### Physical Memory

• Indexed array of bytes
• Indexes are ‘memory addresses’, or pointers
• Two properties
• Volatile - data lost when powered off
• Non-volatile - data remains when powered off
• Can be big-endian or little-endian

## Data Representation

• Characters

• ASCII (7-bit values), UTF-8 (8-bit values)

• UTF-8 uses a variable length encoding as follows

#bytes #bits Byte 1 Byte 2 Byte 3 Byte 4
1 7 0xxxxxxxx - - -
2 11 110xxxxx 10xxxxxx - -
3 16 1110xxxx 10xxxxxx 10xxxxxx -
4 21 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
• For example, UTF-8 to binary of U+1D536

0x1D536 = 0001 1101 0101 0011 0110    // there are 17 significant bits, use 4 byte encoding
= 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
= 11110000 10011101 10010100 10110110

• Integers

• Can represent as a signed decimal (0..9), unsigned hexadecimal (0..F) or a signed octal (0..7)
• int - can store values from $-2^{31} … 2^{31}-1$
• Can represent negative values using
• signed magnitude - first bit is sign, rest are magnitude

• ones complement - inverting all bits in a number (keeping the original sign)

• twos complement - inverting all bits in a number and adding 1 (keeping the original sign)

• For example, converting to twos complement

111010100                  // sign is 1, so negative result
000101011                  // flip all the bits
000101011 + 1 = 000101100  // add the value 1
= -(44)                    // result is negative 44

• unsigned int - can store values from $0…2^{32} - 1$
• Pointers

• Size of pointers is the same for all data types
• For any pointer T *p, p++ increases p by sizeof(T)
• Floats

• Two types
• float - typically 32 bits
• double - typically 64 bits
• IEEE 754 standard

• Stored with 1 bit for the sign, 8 bits for the exponential and 23 bits for the fraction

• Converting from binary to decimal

0 10000010 01000000000000000000000

sign = 0 (positive)
exp = 10000010 = (130 - 127 = 3)
frac = 01000000000000000000000 = 2^(-2)

= +1.25 x 2^3
= 10

• Converting from decimal to binary

150.75

150 = 10010110
.75 = 11

= 10010110.11
= 1.001011011 x 2^7

sign = 0 (positive)
exp = 10000110 (127 + 7 = 134)
frac = 00101101100000000000000 (001011011 + remaining digits)

= 0 10000110 00101101100000000000000

• Arrays

• Have a type and size
• Laid out adjacent in memory
• Structs

• Have a name and a type

• Padding may be needed between elements in struct

• Can have bit wise structs

struct _object { // comprised of two 32-bit integers
unsigned int  red    : 5,  // 5 bits for red
blue   : 5,  // 5 bits for blue
green  : 5,  // 5 bits for green
pad    : 1,  // 1 bit to pad to short
ident  : 16; // 16 bits for object ID
unsigned int  height : 6,  // 6 bits for object height
width  : 6,  // 6 bits for object width
xcoord : 9,  // 9 bits for x-coordinate
ycoord : 9;  // 9 bits for y-coordinate
};

struct _object oval;
...
oval.red = 4; oval.blue = 31; oval.green = 15;
oval.height = 5; oval.width = 15;

• Unions

• Multiple interpretations for a single piece of memory
• Size of union is the size of the largest element of the union
union Tag {
Type1  Member1;
Type2  Member2;
Type3  Member3;
...
} uvar;

• Enums

• A set of distinct named values
typedef enum { RED, YELLOW, BLUE } PrimaryColours;


## Instruction Set Architectures

• A typical modern CPU has
• A set of data registers
• A set of control registers
• An arithmetic logic unit (ALU)
• A set of simple instructions
• Two broad families of instruction set architectures
• RISC - reduced instruction set computer
• CICS - complex instruction set computer

### Fetch Execute Cycle

All CPUs have program execution logic like:

while (1) {
instruction = memory[PC]
PC++  // Program Counter move to next instruction
if (instruction == HALT)
break
else
execute(instruction)
}


## MIPS

• A simple architecture
• Has:
• 32 x 32-bit general purpose registers
• 16 x 64-bit double precision registers
• Program Counter is a 32 bit register
• HI and LO for storing results of multiplication and division

• Memory addresses can be given by a symolic name (label) or indirectly via a register
    .data
vec: .space   16      # int vec[4];
.text             # 16 bytes of storage
.globl  main
main:
la  $t0, vec # reg[t0] = &vec li$t1, 5        # reg[t1] = 5
sw  $t1, ($t0)    # vec[0] = reg[t1]
li  $t1, 13 # reg[t1] = 13 sw$t1, 4($t0) # vec[1] = reg[t1] li$t1, -7       # reg[t1] = -7
sw  $t1, 8($t0)   # vec[2] = reg[t1]
li  $t2, 12 # reg[t2] = 12 li$t1, 42       # reg[t1] = 42
sw  $t1, vec($t2) # vec[3] = reg[t1]


### SPIM

• Spim is an emulator for the MIPS instruction set

• It reads .s files, converts them to machine code and loads them into memory

• Provides extra instructions such as move

• Provides debugging capabilities and mechanisms to interact with the operating system (i.e. syscalls)

Service Code Arguments Result
print_int 1 $a0 = integer print_float 2 $f12 = float
print_double 3 $f12 = double print_string 4 $a0 = char *
read_int 5   integer in $v0 read_float 6 float in$f0
read_double 7   double in $f0 read_string 8 $a0 = buffer, $a1 = length string in buffer ### Functions • When a function is called • The arguments are evaluated and setup for the function ($a0, $a1) • Control is transferred to the code for the function (jal) • Local variables are created ($s0, $s1) • The function code is executed • The return value is setup ($v0)
• Control transfers back to where the function was called from (jr $ra) • The caller receives the return value • Data associated with the function call is placed on the MIPS stack • Each function allocates a section of the stack (the stack frame) • Example function call and stack frame setup: func: # setup stack frame with registers$s0, $s1,$s2
sw    $fp, -4($sp)
sw    $ra, -8($sp)
sw    $s0, -12($sp)
sw    $s1, -16($sp)
sw    $s2, -20($sp)
la    $fp, -4($sp)
add   $sp,$sp, -20

...

end:
# return value, move $s1 into$v0
move  $v0,$s1

# epilogue
lw    $s2, -16($fp)
lw    $s1, -12($fp)
lw    $s0, -8($fp)
lw    $ra, -4($fp)
la    $sp, 4($fp)
lw    $fp, ($fp)
jr    $ra  ### 2D Arrays • Can be represented as one large chunk of memory (a), or as pointers to rows in memory (b) • To access an element at int arr[row][col] we can use the formula$((row \times rowsize) + col) \times sizeof(int)$• For strategy (a) we can implement it as follows:  li$s0, 0             # sum = 0
li  $s1, 6 # s1 = #rows li$s2, 0             # row = 0
li  $s3, 5 # s3 = #cols li$s4, 0             # col = 0 // redundant
li  $s5, 4 # intsize = sizeof(int) mul$s6, $s3,$s5      # rowsize = #cols*intsize
loop1:
beq  $s2,$s1, end1    # if (row >= 6) break
li   $s4, 0 # col = 0 loop2: beq$s4, $s3, end2 # if (col >= 5) break mul$t0, $s2,$s6     # t0 = row*rowsize
mul  $t1,$s4, $s5 # t1 = col*intsize add$t0, $t0,$t1     # offset = t0+t1
lw   $t0, matrix($t0)  # t0 = *(matrix+offset)
add  $s0,$s0, $t0 # sum += t0 addi$s4, $s4, 1 # col++ j loop2 end2: addi$s2, $s2, 1 # row++ j loop1 end1:  ### Structs • Structs in MIPS are just blocks of memory, where each member can be accessed using the appropriate offset stu1: .space 56 # Student stu1; stu2: .space 56 # Student stu2; # stu is$s1          # Student *stu;

li  $t0 5012345 sw$t0, stu1+0       # stu1.id = 5012345;
li  $t0, 3778 sw$t0, stu1+44      # stu1.program = 3778;

la  $s1, stu2 # stu = &stu2; li$t0, 3707
sw  $t0, 44($s1)      # stu->program = 3707;
li  $t0, 5034567 sw$t0, 0($s1) # stu->id = 5034567;  ## Computer System Architecture ### Operating Systems • Provide an abstraction layer on top of hardware • Characteristics: • Have privileged access to the raw machine • Manage use of machine resources (CPU, disk, memory) • Provide a uniform interface to access machine level operations • Arrange for controlled execution of user programs • Provide multi-tasking and parallelism • Abstractions provided: • Uses, access rights, file system, input/output, processes, communication and networking • Flavours: • Batch (Eniac, early IBM) • Computational jobs run one at a time via a queue • Multi-user (Unix, Linux OSX, Windows) • Multiple jobs run in parallel • Embedded • Smallish, cut-down systems embedded in a device • Real-time • Specialised OS with time guarantees on job completion • Evolution of Unix • 1970’s - Bell Labs delevoped a small OS core written mostly in high level languages and having a small set of simple tools • 1980’s to present - Unix and variants ported to wide variety of architectures and developments in hardware/sortware led to more OS services (databases, networking, GUIs) • CPUs typically run in either privileged mode (full access to all machine operations and memory) or non-privileged mode (limited set of operations and access to part of memory). ### System Calls • Used primarily for input/output (printf()), memory allocation (malloc()), file management (read(), open()), device management (ioctl()), information maintenance (settimeofday()) and communication (pipe(), connect()) • Successful system calls usually return 0, unsuccessful system falls usually return -1 or raise an error errno ## File Systems • Provide a mechanism for managing stored data • Unix file system: • Paths are absolute or relative • Metadata is stored in inodes which hold • Physical location on storage device of file data • File type and file size • Ownership, access permissions, timestamps • Links • Hard links are when multiple directory entries reference the same inode • Symolic links refers to a file containing the path name of another file • Various file system functions // attemps to open an object at Path according to Flags (i.e. O_RDONLY, O_WRONLY, O_APPEND...) int open(char *Path, int Flags); // attemps to release an open file descriptor int close(int FileDesc); // attempts to read Count bytes from FileDesc into Buffer ssize_t read(int FileDesc, void *Buffer, size_t Count); // attempts to write Count bytes from Buffer onto FileDesc ssize_t write(int FileDesc, void *Buffer, size_t Count); // stores metadata associated with FileName into StatBuf int stat(char *FileName, struct stat *StatBuf); // same as stat() but gets data via an open file descriptor int fstat(int FileDesc, struct stat *StatBuf); // same as stat() but doesn't follow symbolic links int lstat(char *FileName, struct stat *StatBuf); // create a new directory called PathName with mode Mode int mkdir(char *PathName, mode_t Mode); // attaches a file system Source to a Target (mount point) with a particular layout/driver FileSysType and with specific properties of the file system Flags int mount(char *Source, char *Target, char *FileSysType, unsigned long Flags, void *data);  ## Memory Management • In early days • One process at a time • The single process could use the entirety of the memory • Addresses within process code are absolute • Nowadays • Multiple processes are loaded into memory at once • Addresses are relative to the other processes and their memory usage • When adding a new process, if there isn’t a large enough free space we can split processes to create a larger space. The process address mapping table must be updated • Process memory • Each ‘chunk’ of address space is called a page • All paegs are the same size$P$(PageSize) • Process memory is spread across$ceiling(ProcSize/P)$pages • Page$i$has addresses$A$in range$i \times P \le A \lt (i + 1) \times P$• Address mapping table • Each process has an array of page entries • Each page entry contains start address of one chunk • Can compute index of relevant page entry by$A / P$• Can compute offset within page by$A \ \% \ P\$

To map from process address (virtual address) to physical address:

typedef struct {char status, uint frameNo, ...} PageData;

PageData *AllPageTables[maxProc]; // one entry for each process

PageData *PageTable = AllPageTables[pid];
uint pageno = PageNumberFrom(Vaddr);
uint offset = OffsetFrom(Vaddr);
if (PageTable[pageno].status != Loaded) {
// load page into free frame
// set PageTable[pageno]
}
uint frame = PageTable[pageno].frameNo;
return frame * P + offset;
}


### Virtual Memory

• Divides process memory space into fixed sized pages
• On-demand loading of process pages into physical memory
• Pages/frames are usually 512B to 8KB in size
• Each frame holds one page of process address space
• Requesting a non-loaded page generates a page fault
• To handle page faults, we can find a free (unused) page frame in memory and use that
• When finding free page we can either suspend the requesting process until a page is free, or replace a currently loaded page based on some condition (possibly by when it was loaded)
• Page replacement
• When a page is replaced
• If it has been modified, save it to disk
• Get its frame number and give it to the requestor
• To decide which page to replace, we can use a measure of usefulness. This may be implemented using a priority queue. The page with the lowest usefulness is replaced
• A useful heuristic is LRU replacement which refers to replacing a page that hasn’t been used recently and may not be needed again
• Other heuristics include FIFO and clock sweep

### Cache Memory

• Small, fast and close to the CPU
• Holds parts of RAM that are heavily used
• Transfers data to and from RAM in cache blocks

### Memory Management Hardware

• Specialised hardware (MMU) is provided to perform address translation efficiently
• Has a translation lookaside buffer (TLB) which is a lookup table containing virtual and physical address pairs

## Process Management

• A process is an instance of an executing program

• The OS manages multiple simultaneous processed using:

• Context - a collection of information for one process
• Static information - program code and data
• Dynamic state - heap, stack, registers, program counter
• OS-supplied state - environment variables, stdin, stdout
• Context switch
• Saves context for one process
• Restores context for another process
• Process control block - info about each process including:
• Identifier - unique process ID (pid)
• Status - running, ready, suspended or dead
• State: registers, program counter
• Privileges - owner, group
• If suspended, the event being waited for
• Memory management info - reference to page table
• Accounting - CPU time used, amount of input/output done
• Input/Output - open file descriptors
• Process info is split across process table entry and user structure

• Process table - kernel data structure describing all processes
• User structure - kernel data structure describing runtime state
• PIDs

• Every process in Unix is allocated a unique, positive integer PID
• PID 0 is the scheduler
• PID 1 is init
• Zombie processes

• A process which has exited but signal SIGCHLD not handed
• Orphan process

• A process whose parent has exited
• Process related system calls

// creates new process by duplicating the calling process
pid_t fork(void);

// returns the PID of the current process
pid_t getpid();

// returns the parent PID of the current process
pid_t getppid();

// set the process group ID of specified process
int setpgid(pid_t pid, pid_t pgid);

// returns the process group ID of specified process
pid_t getpgid(pid_t pid);

// pause current process until process pid changes state
pid_t waitpid(pid_t pid, int *status, int options);

// pauses until one of the child processes terminates
pid_t wait(int *status);

// send signal SigID to process ProcID
int kill(pid_t ProcID, int SigID);

// terminates current process, closes any open file descriptors
void _exit(int status);

// terminates current process, flushes stdio buffers, then behaves like _exit()
void exit(int status);

// generates SIGABRT signal
void abort(void);

// replaces current process by executing Path object, passes arrays of strings to new process
int execve(char *Path, char *Argv[], char *Envp[]);


### Signals

• Generated from a variety of sources (kill(), syscall, divide by zery, CTR-C)

• man signal for a list of signals

• Process can choose to ignore signals, or can terminate, terminate and dump core, stop, or continue

• Signal handlers are functions that are invoked in response to a signal

• They know which signal was invoked
• Carries out an appropriate action
• Note for below: typedef void (*SigHnd)(int);
// define how to handle a particular signal
// SigID is one of the OS-defined signals
// Handler could be SIG_IGN, or SIG_DFL or user defined
SigHnd signal(int SigID, SigHnd Handler);

// sigID is one of the OS-defined signals
// newAct defines how signal should be handled
// oldAct saves a copy of how signal was handled
int sigaction(int sigID, struct sigaction *newAct, struct sigaction *oldAct);


### Interrupts

• Signals that cause normal process execution to be suspended
• An interrupt handler carries out tasks related to interrupt
• Control is then returned to the original process

### Exceptions

• Unexpected but not unanticipated conditions which arise during computation

• Multiple processes are “active” at the same time

### Scheduling

• Selecting which process should run next

• Processes are organised into priority queue(s) where “highest” priority process is always at head of queue

• Priority determined by multiple factors

• System processes have higher priority
• Longer running processes may have lower priority
• Memory intensitve processes may have lower priority
• Abstract view of the OS scheduler

function onTimerInterrupt() {
save state of currently executing process
newPID = dequeue(runnableProcesses)
setup state of newPID
- make process's Page Table active
- load pages in working set
- load process's registers
transfer control to newPID
- leave kernel mode
- set PC to saved PC from process
}


## Device Management

### I/O Devices

• Hard Disks
• Address specified by track and sector
• Typical cost: 10ms seek + 5ms latency + 0.1ms transfer
• Solid State Disks
• High capacity (GB), high cost, reading faster than writing

### Device Drivers

• Code chunks to control an I/O device, often written in assembly

• Each type of device has its own unique access protocol

### Memory Mapped I/O

• Operating system defines special memory address
• User programs perform I/O by getting/setting data into memory

### Devices On Unix

• Devices can be accessed via the file system under /dev
• Two standard types of device files
• Character devices - provide unbuffered direct access to hardware devices
• Block devices - provide buffered access to hardware devices
• Devices can be manipulated by open(), read(), write() etc

### Buffered I/O

• Devices are typically very slow compared to CPU speed
• Using a read() from a device for each byte is inefficient
• OS uses a collection of buffers to hold data from devices

## Networks

• A network is an interconnected collection of computers
• Internet communications are based on a 5 layer stack:
• Physical layer - bits on wires or fibre optics or radio
• Link layer - ethernet, MAC addressing, CSMA…
• Network layer - routing protocols, IP
• Transport layer - TCP/UDP
• Application layer - DNS, HTTP, FTP, SMTP…
• Network protocols govern all communication activity on a network
• Protocols are defined in all of the layers
• Examples include PPP (link layer), IP (network layer), TCP (transport layer), HTTP (application layer)
• Client-server architecture is a common way of structuring network communication
• The server is the data provided, that processes and waits for requests
• The client is the data consumer that sends requests and receives responses
• Servers must have a unique internet wide address
• Includes an IP address and a port number, for example - 128.119.245.12:80
• Some standard port numbers include 22 (SSH), 25 (SMTP), 53 (DNS), 80 (HTTP), 443 (HTTPS)
• An IP address is a unique identifier for a host on a network, can be IPv4 (32 bit) or IPv6 (128 bit)
• DNS
• The Doman Name System provides a mapping from name to IP address
• Use the host command to view a servers IP address
• Is decentralised and distributed
• Two styles of name resolution
• Iterated query - the client gets a response and send a request to a different server
• Recursive query - the client contacts a server and the query propagates until the name is resolved

### Network Architecture In Depth

• Transport Layer
• Deals with data integrity (e.g. no packet loss), timing (delay/ping), throughput and security (tls)
• Two main transport layer protocols - TCP (reliable, bytestream, man tcp) and UDP (unreliable, simple, fast, man udp)
• Network Layer
• Provides a way for processes/hosts to communicate
• Internet Protocol (IP) is a network layer that provides host addressing, routing of packets, splitting and reassembly of large packets
• Each host maintains a routing table and uses subnets to reduce table size
• Takes packets from the network layer and transmits them
• Each host contains a network interface card (NIC)
• Services provided by link layer include: flow control, error detection and error correction
• An example is ethernet

## Concurrency

• Parallelism is when multiple comutations are executed simultaneously
• Consurrency is when multiple processes are running simultaneously
• One method for creating concurrent taks is using fork() which
• Creates a new child process that executes concurrently with parent
• Inherits some state from parent
• Runs in its own address space
• Another method is threads which are
• Independent of each other
• Share parent process state
• Share address space
• Effects of poorly controlled concurrency
• Nondeterminism - same code on different runs produces different results
• Deadlock - a group of processes end up waiting for each other
• Starvation - one process keeps missing access to resource
• Concurrency control aims to provide correct sequencing of interactions between processes
• Shared memory based - semaphores
• Uses shared variable which is manipulated atomically
• Blocks if access unavailabble, unblocks when available
• Message passing bases - send/receive
• Processes communicate by sending and receiving messages
• Receiver can block waiting for message to arrive
• Sender can block waiting for message to be received
• Producer Consumer Problem
• The producer’s job is to generate data, put it into a buffer, and start again. At the same time, the consumer is consuming the data (i.e., removing it from the buffer), one piece at a time
• A problem arises when
• The consumer checks and finds the buffer empty, and decides to pause
• Before pausing the consumer gets timed out
• The producer puts an item in the empty buffer
• The producer signals the consumer
• Because the consumer is not paused the signal is lost
• The consumer is resumed after the time out and pauses
• The producer resumes and adds items into the buffer until full, and then pauses
• Each process is paused waiting for a signal from the other
• The problem is called deadlock, and to fix it we use semaphores
• File Locking
• Uses flock()
• man 2 flock
• Pipes
• Provides buffered I/O between producer and consumer
• Uses pipe()
• Usually fork() called after pipe opened, so child and parent share pipe fd’s and can communicate with each other
• man 2 pipe
• Message Queues
• Provides a mechanism for unrelated processes to pass information along a buffered channel shared by many processes
• man 7 mq_overview
• Sockets
• An IPC endpoint for communication
• Commonly used for client server systems
• Server creates a socket which binds to an address and listens for connections
• Client creates a socket which connects to the socket and writes/reads from a socket
• Uses a struct sockaddr_in
• sin_family - domain (AF_UNIX or AF_INET)
• sin_port - port number
• sin_addr - struct containing host address
• man 2 socket
// creates a new thread with specified Attributes
// thread starts by executing Func() with Arg

// returns a pthread_t for current thread

// compares two thread IDs and returns 0 if the same, otherwise non-zero

// suspend execution until thread T terminates
// pthread_exit() value is placed in *value_ptr
// if T has already exited, does not wait

// terminate execution of thread, and do some cleaning up
// stores a return value in *value_ptr

// create a semaphore object, and set initial value
int sem_init(sem_t *Sem, int Shared, uint Value);

// try to decrement
// blocks if Sem == 0
int sem_wait(sem_t *Sem);

// increment the value of semaphore Sem
int sem_post(sem_t *Sem);

// free all memory associated with semaphore Sem
int sem_destroy(sem_t *Sem);

int flock(int FileDesc, int Operation);

// open two file descriptors
// fd[0] is opened for reading, fd[1] is opened for writing
int pipe(int fd[2]);

// opens a process by creating a bidirectional pipe, forking, and invoking the shell Cmd
FILE *popen(char *Cmd, char *Mode);

// create a new message queue, or open existing one
mqd_t mq_open(char *Name, int Flags);

// finish accessing message queue MQ
int mq_close(mqd_t *MQ);

// adds message Msg to message queue MQ with a given priority Prio
int mq_send(mqd_t MQ, char *Msg, int Size, uint Prio);

// removes highest priority message from queue MQ
int mq_receive(mqd_t MQ, char *Msg, int Size, uint *Prio);

// creates a socket with a Domain, Type and Protocol
int socket(int Domain, int Type, int Protocol);

// associates an open socket with an address

// wait for connections on socket Sockfd
// allow at most Backlog connections to be queued up
int listen(int Sockfd, int Backlog);

// Sockfd has been created, bound and is listening

// connects the socket Sockfd to address Addr


## Exam Tips

Use man for looking up system calls, unix variables etc

Section Description
1 User commands
2 System calls
3 C Library Functions
4 Devices and Special Files
5 File Formats and Conventions
6 Games and Screensavers
7 Misc
8 System Administration tools and Daemons

Use apropos <search_term> to search for man pages

Use man -K <search_term> to brute force search man pages for a specific keyword

Use bc (1) To Convert Between Bases

# ibase is the input base, obase is the output base, <string> is the input string
echo 'obase=2; ibase=16; <string>' | bc

# converting hex to binary
echo 'obase=2; ibase=16; ABC123' | bc

# converting octal to binary
echo 'obase=2; ibase=8; 12345' |  bc

# converting hex to decimal - leave out the obase
echo 'ibase=16; ABC123' | bc