慶應義塾大学
2013年度 春学期

システム・ソフトウェア
System Software / Operating Systems

2013年度春学期 火曜日1時限
科目コード: 60730
開講場所:SFC
授業形態:講義
担当: Rodney Van Meter
E-mail: rdv@sfc.keio.ac.jp

第3回 4月日23 プロセスとスレッド
Lecture 3, April 23: Processes and Threads, Parallel Programming

Today's Picture

The average age in Mission Control for Apollo 17 (the last mission to the moon) was 26.

Apollo 17 Mission
						   Control, average
						   age 26

Who is this guy?

Butler Lampson, from Microsoft

Outline

Get Google to translate this page. このページをグーグルに翻訳をまかせよう!

Class Discussion of Lampson

Last week, you were asked to read Lampson, Hints for Computer System Design. What did you learn? (We will discuss some of the slogans in the figure from the paper.)

What's a Process?

Several times already this term I've used the term "process". So what is a process, anyway? A process is an instance of a running program on a multitasking or multiprogrammed OS. If we ignore performance, the concept of a process is what allows an application program to think that it has the entire computer to itself. (In systems with virtual memory, the process abstraction also allows programs to think they have lots of memory, whether or not they really do.)

Segments

セグメントはメモリのエリアです。いくつかの書類がある:
A "segment" of a Unix process is one of its primary data areas: the text segment is the program itself (including shared libraries), the data segment is for most variables (of several types; those created using the malloc() memory acquisition routine, primarily), and the stack segment holds, well, the stack. The portion of the data segment that is dynamically allocated and deallocated is often called the memory heap.

Some operating systems explicitly support requests for memory already filled with zeroes or not; the choice of which to use is for efficiency. The choice the OS makes on whether or not to supply memory that is not zero-filled is both an efficiency and a security issue. In Unix systems, application programs cannot assume that freshly-allocated memory contains zeroes, but if it does not, it is usually because the memory allocator has reassigned memory that the same process has recently freed. Handing out memory that other processes have recently freed would allow one process to read some of another's memory, without permission!

Process control structures

From include/linux/sched.h:

struct task_struct {
	volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
	struct thread_info *thread_info;
	atomic_t usage;
	unsigned long flags;	/* per process flags, defined below */
	unsigned long ptrace;

	int lock_depth;		/* BKL lock depth */

#ifdef CONFIG_SMP
#ifdef __ARCH_WANT_UNLOCKED_CTXSW
	int oncpu;
#endif
#endif
	int load_weight;	/* for niceness load balancing purposes */
	int prio, static_prio, normal_prio;
	struct list_head run_list;
	struct prio_array *array;

	unsigned short ioprio;
#ifdef CONFIG_BLK_DEV_IO_TRACE
	unsigned int btrace_seq;
#endif
	unsigned long sleep_avg;
	unsigned long long timestamp, last_ran;
	unsigned long long sched_time; /* sched_clock time spent running */
	enum sleep_type sleep_type;

	unsigned long policy;
	cpumask_t cpus_allowed;
	unsigned int time_slice, first_time_slice;

#if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT)
	struct sched_info sched_info;
#endif

	struct list_head tasks;
	/*
	 * ptrace_list/ptrace_children forms the list of my children
	 * that were stolen by a ptracer.
	 */
	struct list_head ptrace_children;
	struct list_head ptrace_list;

	struct mm_struct *mm, *active_mm;

/* task state */
	struct linux_binfmt *binfmt;
	long exit_state;
	int exit_code, exit_signal;
	int pdeath_signal;  /*  The signal sent when the parent dies  */
	/* ??? */
	unsigned long personality;
	unsigned did_exec:1;
	pid_t pid;
	pid_t tgid;

#ifdef CONFIG_CC_STACKPROTECTOR
	/* Canary value for the -fstack-protector gcc feature */
	unsigned long stack_canary;
#endif
	/* 
	 * pointers to (original) parent process, youngest child, younger sibling,
	 * older sibling, respectively.  (p->father can be replaced with 
	 * p->parent->pid)
	 */
	struct task_struct *real_parent; /* real parent process (when being debugged) */
	struct task_struct *parent;	/* parent process */
	/*
	 * children/sibling forms the list of my children plus the
	 * tasks I'm ptracing.
	 */
	struct list_head children;	/* list of my children */
	struct list_head sibling;	/* linkage in my parent's children list */
	struct task_struct *group_leader;	/* threadgroup leader */

	/* PID/PID hash table linkage. */
	struct pid_link pids[PIDTYPE_MAX];
	struct list_head thread_group;

	struct completion *vfork_done;		/* for vfork() */
	int __user *set_child_tid;		/* CLONE_CHILD_SETTID */
	int __user *clear_child_tid;		/* CLONE_CHILD_CLEARTID */

	unsigned long rt_priority;
	cputime_t utime, stime;
	unsigned long nvcsw, nivcsw; /* context switch counts */
	struct timespec start_time;
/* mm fault and swap info: this can arguably be seen as either mm-specific or thread-specific */
	unsigned long min_flt, maj_flt;

  	cputime_t it_prof_expires, it_virt_expires;
	unsigned long long it_sched_expires;
	struct list_head cpu_timers[3];

/* process credentials */
	uid_t uid,euid,suid,fsuid;
	gid_t gid,egid,sgid,fsgid;
	struct group_info *group_info;
	kernel_cap_t   cap_effective, cap_inheritable, cap_permitted;
	unsigned keep_capabilities:1;
	struct user_struct *user;
#ifdef CONFIG_KEYS
	struct key *request_key_auth;	/* assumed request_key authority */
	struct key *thread_keyring;	/* keyring private to this thread */
	unsigned char jit_keyring;	/* default keyring to attach requested keys to */
#endif
	/*
	 * fpu_counter contains the number of consecutive context switches
	 * that the FPU is used. If this is over a threshold, the lazy fpu
	 * saving becomes unlazy to save the trap. This is an unsigned char
	 * so that after 256 times the counter wraps and the behavior turns
	 * lazy again; this to deal with bursty apps that only use FPU for
	 * a short time
	 */
	unsigned char fpu_counter;
	int oomkilladj; /* OOM kill score adjustment (bit shift). */
	char comm[TASK_COMM_LEN]; /* executable name excluding path
				     - access with [gs]et_task_comm (which lock
				       it with task_lock())
				     - initialized normally by flush_old_exec */
/* file system info */
	int link_count, total_link_count;
#ifdef CONFIG_SYSVIPC
/* ipc stuff */
	struct sysv_sem sysvsem;
#endif
/* CPU-specific state of this task */
	struct thread_struct thread;
/* filesystem information */
	struct fs_struct *fs;
/* open file information */
	struct files_struct *files;
/* namespaces */
	struct nsproxy *nsproxy;
/* signal handlers */
	struct signal_struct *signal;
	struct sighand_struct *sighand;

	sigset_t blocked, real_blocked;
	sigset_t saved_sigmask;		/* To be restored with TIF_RESTORE_SIGMASK */
	struct sigpending pending;

	unsigned long sas_ss_sp;
	size_t sas_ss_size;
	int (*notifier)(void *priv);
	void *notifier_data;
	sigset_t *notifier_mask;
	
	void *security;
	struct audit_context *audit_context;
	seccomp_t seccomp;

/* Thread group tracking */
   	u32 parent_exec_id;
   	u32 self_exec_id;
/* Protection of (de-)allocation: mm, files, fs, tty, keyrings */
	spinlock_t alloc_lock;

	/* Protection of the PI data structures: */
	spinlock_t pi_lock;

#ifdef CONFIG_RT_MUTEXES
	/* PI waiters blocked on a rt_mutex held by this task */
	struct plist_head pi_waiters;
	/* Deadlock detection and priority inheritance handling */
	struct rt_mutex_waiter *pi_blocked_on;
#endif

#ifdef CONFIG_DEBUG_MUTEXES
	/* mutex deadlock detection */
	struct mutex_waiter *blocked_on;
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
	unsigned int irq_events;
	int hardirqs_enabled;
	unsigned long hardirq_enable_ip;
	unsigned int hardirq_enable_event;
	unsigned long hardirq_disable_ip;
	unsigned int hardirq_disable_event;
	int softirqs_enabled;
	unsigned long softirq_disable_ip;
	unsigned int softirq_disable_event;
	unsigned long softirq_enable_ip;
	unsigned int softirq_enable_event;
	int hardirq_context;
	int softirq_context;
#endif
#ifdef CONFIG_LOCKDEP
# define MAX_LOCK_DEPTH 30UL
	u64 curr_chain_key;
	int lockdep_depth;
	struct held_lock held_locks[MAX_LOCK_DEPTH];
	unsigned int lockdep_recursion;
#endif

/* journalling filesystem info */
	void *journal_info;

/* VM state */
	struct reclaim_state *reclaim_state;

	struct backing_dev_info *backing_dev_info;

	struct io_context *io_context;

	unsigned long ptrace_message;
	siginfo_t *last_siginfo; /* For ptrace use.  */
/*
 * current io wait handle: wait queue entry to use for io waits
 * If this thread is processing aio, this points at the waitqueue
 * inside the currently handled kiocb. It may be NULL (i.e. default
 * to a stack based synchronous wait) if its doing sync IO.
 */
	wait_queue_t *io_wait;
/* i/o counters(bytes read/written, #syscalls */
	u64 rchar, wchar, syscr, syscw;
#if defined(CONFIG_TASK_XACCT)
	u64 acct_rss_mem1;	/* accumulated rss usage */
	u64 acct_vm_mem1;	/* accumulated virtual memory usage */
	cputime_t acct_stimexpd;/* stime since last update */
#endif
#ifdef CONFIG_NUMA
  	struct mempolicy *mempolicy;
	short il_next;
#endif
#ifdef CONFIG_CPUSETS
	struct cpuset *cpuset;
	nodemask_t mems_allowed;
	int cpuset_mems_generation;
	int cpuset_mem_spread_rotor;
#endif
	struct robust_list_head __user *robust_list;
#ifdef CONFIG_COMPAT
	struct compat_robust_list_head __user *compat_robust_list;
#endif
	struct list_head pi_state_list;
	struct futex_pi_state *pi_state_cache;

	atomic_t fs_excl;	/* holding fs exclusive resources */
	struct rcu_head rcu;

	/*
	 * cache last used pipe for splice
	 */
	struct pipe_inode_info *splice_pipe;
#ifdef	CONFIG_TASK_DELAY_ACCT
	struct task_delay_info *delays;
#endif
};

Unix fork()

fork() is the only way in Unix to make a new process. The system call copies the existing process and creates a child process. The child process inherits everything, including open file descriptors. In early versions of Unix, fork had to literally copy every page of data memory, but the program (or text segment) could be shared, because it is generally protected from being written to using processor page protection bits. However, copying all of the data memory can be expensive. Therefore, the vfork() system call was later introduced; we won't go into details, but vfork() stops the parent from executing until the child is done with the memory. (The child should not actually touch the memory, however.) Modern systems generally implement fork() by copying only the page table of the calling process, and setting the data pages to copy on write. We will discuss copy on write when we cover memory management.

fork() is used very frequently. Every time the user requests execution of a program (via the shell), fork() is called. The child process then generally calls exec(), which replaces the currently running program (generally the shell) in this (the child) process, loading another program from disk, if necessary, and starting it. The parent can later choose to wait for the child process to finish, or the parent can continue executing its own work.

Most of the work in Linux is actually done in the copy_process function. do_fork() is fairly short. copy_process makes new copies of certain structures, and creates new pointers to reference-counted objects for things that need to be shared.

From kernel/fork.c:

/*
 *  Ok, this is the main fork-routine.
 *
 * It copies the process, and if successful kick-starts
 * it and waits for it to finish using the VM if required.
 */
long do_fork(unsigned long clone_flags,
	      unsigned long stack_start,
	      struct pt_regs *regs,
	      unsigned long stack_size,
	      int __user *parent_tidptr,
	      int __user *child_tidptr)
{
	struct task_struct *p;
	int trace = 0;
	struct pid *pid = alloc_pid();
	long nr;

	if (!pid)
		return -EAGAIN;
	nr = pid->nr;
	if (unlikely(current->ptrace)) {
		trace = fork_traceflag (clone_flags);
		if (trace)
			clone_flags |= CLONE_PTRACE;
	}

	p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, nr);
	/*
	 * Do this prior waking up the new thread - the thread pointer
	 * might get invalid after that point, if the thread exits quickly.
	 */
	if (!IS_ERR(p)) {
		struct completion vfork;

		if (clone_flags & CLONE_VFORK) {
			p->vfork_done = &vfork;
			init_completion(&vfork);
		}

		if ((p->ptrace & PT_PTRACED) || (clone_flags & CLONE_STOPPED)) {
			/*
			 * We'll start up with an immediate SIGSTOP.
			 */
			sigaddset(&p->pending.signal, SIGSTOP);
			set_tsk_thread_flag(p, TIF_SIGPENDING);
		}

		if (!(clone_flags & CLONE_STOPPED))
			wake_up_new_task(p, clone_flags);
		else
			p->state = TASK_STOPPED;

		if (unlikely (trace)) {
			current->ptrace_message = nr;
			ptrace_notify ((trace << 8) | SIGTRAP);
		}

		if (clone_flags & CLONE_VFORK) {
			wait_for_completion(&vfork);
			if (unlikely (current->ptrace & PT_TRACE_VFORK_DONE)) {
				current->ptrace_message = nr;
				ptrace_notify ((PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP);
			}
		}
	} else {
		free_pid(pid);
		nr = PTR_ERR(p);
	}
	return nr;
}
Other operating systems treat process creation and program execution (and, in conjunction, the shell) very differently from Unix; in fact, Unix's approach often seems weird when you first encounter it. In the venerable VMS (Virtual Memory System) from Digital Equipment Corporation (now owned by HP), executing a program loads the program into memory without replacing or overwriting the memory for DCL, the Digital Command Language. DCL resides in a special portion of memory, and is always present throughout the life of the process. This is handy when you want to be able to call functions such as DCL's command line editor, but most of the time is wasteful of address space, and possibly other resources. Process creation in Unix is a heavy, complicated process, but not nearly as bad as in VMS, so in VMS great pains are taken to make process creation be a rare event.

Threads

So far, we have discussed a process as if it involves only a single program, running from begin to end. For many processes, this is accurate. However, modern OSes support multiple threads of control in a single process.

A process is an abstraction of both memory and the CPU. It contains one or more threads. The thread is the abstraction of the CPU, including the PC (program counter) and all of the registers. The stack is part of the thread, since its state depends on the current place in the program where you are running. Thus, separate threads place their local frames (local variables for a function) on separate stacks, but memory in the heap and statically-allocated global variables are shared.

Threads can be implemented by the kernel itself, or in user space. User space implementations are lighter weight, and may use a threads library that is itself portable, providing more portable software. User threads also allow the user to control scheduling.

The POSIX standard defines an API and semantics for threads. Here is some information from the Linux man (manual) page for pthreads:

PTHREADS(7)                Linux Programmer’s Manual               PTHREADS(7)

名前
       pthreads - POSIX スレッド

説明
       POSIX.1 は、一般に POSIX スレッドや Pthreads として知られるスレッド・プ
       ログラミングのインタフェース群 (関数、ヘッダファイル) を規定している 。
       一 つのプロセスは複数のスレッドを持つことができ、全てのスレッドは同じプ
       ログラムを実行する。これらのスレッドは同じ大域メモリ (データとヒープ 領
       域) を共有するが、各スレッドは自分専用のスタック (自動変数) を持つ。

       POSIX.1  はスレッド間でどのような属性を共有するかについても定めている (
       つまり、これらの属性はスレッド単位ではなくプロセス全体で共通である):

       -  プロセス ID

       -  親プロセス ID

       -  プロセスグループ ID とセッション ID

       -  制御端末

       -  ユーザ ID とグループ ID

       -  オープンするファイルディスクリプタ

       -  レコードのロック (fcntl(3) 参照)

       -  シグナルの配置

       -  ファイルモード作成マスク (umask(2))

       -  カレント・ディレクトリ (chdir(2)) とルート・ディレクトリ (chroot(2))

       -  インターバル・タイマ (setitimer(2)) と POSIX タイマ (timer_create())

       -  nice 値 (setpriority(2))

       -  リソース制限 (setrlimit(2))

       -  CPU 時間 (times(2)) とリソース (getrusage(2)) の消費状況の計測

       スタックについても、POSIX.1 はどのような属性が個々のスレッドで独立に 管
       理されるかを規定している:

       -  スレッド ID pthread_t データ型)

       -  シグナルマスク (pthread_sigmask())

       -  errno 変数

       -  代替シグナルスタック (sigaltstack(2))

       -  リ ア ル タイム・スケジューリングのポリシーと優先度 (sched_setsched-
          uler(2) と sched_setparam(2))

       以下の Linux 特有の機能もスレッド単位である:

       -  ケーパビリティ (capabilities(7) 参照)

       -  CPU affinity (親和度) (sched_setaffinity(2))



PTHREADS(7)                Linux Programmer's Manual               PTHREADS(7)

NAME
       pthreads - POSIX threads

DESCRIPTION
       POSIX.1  specifies  a  set  of interfaces (functions, header files) for
       threaded programming commonly known as POSIX threads, or  Pthreads.   A
       single process can contain multiple threads, all of which are executing
       the same program.  These threads share the same global memory (data and
       heap  segments),  but  each  thread  has its own stack (automatic vari-
       ables).

       POSIX.1 also requires that threads share a range  of  other  attributes
       (i.e., these attributes are process-wide rather than per-thread):

       -  process ID

       -  parent process ID

       -  process group ID and session ID

       -  controlling terminal

       -  user and group IDs

       -  open file descriptors

       -  record locks (see fcntl(2))

       -  signal dispositions

       -  file mode creation mask (umask(2))

       -  current directory (chdir(2)) and root directory (chroot(2))

       -  interval timers (setitimer(2)) and POSIX timers (timer_create())

       -  nice value (setpriority(2))

       -  resource limits (setrlimit(2))

       -  measurements of the consumption of CPU time (times(2)) and resources
          (getrusage(2))

       As well as the stack, POSIX.1 specifies that various  other  attributes
       are distinct for each thread, including:

       -  thread ID (the pthread_t data type)

       -  signal mask (pthread_sigmask())

       -  the errno variable

       -  alternate signal stack (sigaltstack(2))

       -  real-time  scheduling policy and priority (sched_setscheduler(2) and
          sched_setparam(2))

       The following Linux-specific features are also per-thread:

       -  capabilities (see capabilities(7))

       -  CPU affinity (sched_setaffinity(2))


Working with threads is very much like writing code for multitasking embedded operating systems, such as, say, Nucleus or VxWorks, and as such are a very important concept, and learning to use them is valuable. However, synchronization bugs are common; libraries that simplify locking are very useful, but often so conservative that it is difficult for more than one thread at a time to run. When well-managed, threads allow for highly efficient shared-memory multiprocessing. However, the trend in parallel processing is toward message passing systems, which obviously map well to distributed-memory multicomputers as well as the Internet itself.

Kernel Process Management

We aren't going into detail on this topic at the moment, but a few basic concepts are in order. A Unix (or Linux) kernel manages the kernel structures described above in a doubly-linked list (is that kept in some kind of order? not sure). Individual processes are named via their PID, or process ID. Because each process has exactly one parent, and any process can create one or more children, the processes in a system are structured in a process tree. A (sub-)tree of processes can all be managed as a group. One consequence of the requirement that a process must have a parent is that no process with children can be completely destroyed until all of its children have exited.

A very useful modern innovation in Unix-style operating systems is the /proc file system. By looking in the directory /proc, one can find out many facts about the running state of the entire system and of individual processes.

Signals, Interrupts, and Exits

Signals, as Unix terms them, are the most fundamental way of letting a process know that something has happened in the system. They may result from some action of the process itself, such as a floating point exception or an illegal instruction, or may be sent from one process to another using the system call kill(). kill() might seem like an odd name, but signal() is already taken as the system call a process uses to install its own signal handler, and kill() is most often used to actually kill a running program.

       最初に、POSIX.1-1990 に定義されているシグナルを示す。

       シグナル      値      動作   コメント
       ------------------------------------------------------------------------
       SIGHUP         1      Term   制御端末(controlling terminal)のハングアッ
                                    プ検出、または制御しているプロセスの死
       SIGINT         2      Term   キーボードからの割り込み (Interrupt)
       SIGQUIT        3      Core   キーボードによる中止 (Quit)
       SIGILL         4      Core   不正な命令
       SIGABRT        6      Core   abort(3) からの中断 (Abort) シグナル
       SIGFPE         8      Core   浮動小数点例外
       SIGKILL        9      Term   Kill シグナル
       SIGSEGV       11      Core   不正なメモリ参照
       SIGPIPE       13      Term   パイプ破壊: 読み手の無いパイプへの書き出し
       SIGALRM       14      Term   alarm(2) からのタイマーシグナル
       SIGTERM       15      Term   終了 (termination) シグナル
       SIGUSR1    30,10,16   Term   ユーザ定義シグナル 1
       SIGUSR2    31,12,17   Term   ユーザ定義シグナル 2
       SIGCHLD    20,17,18   Ign    子プロセスの一旦停止 (stop) または終了
       SIGCONT    19,18,25   Cont   一旦停止 (stop) からの再開
       SIGSTOP    17,19,23   Stop   プロセスの一旦停止 (stop)
       SIGTSTP    18,20,24   Stop   端末 (tty) より入力された一旦停止 (stop)
       SIGTTIN    21,21,26   Stop   バックグランドプロセスの tty 入力
       SIGTTOU    22,22,27   Stop   バックグランドプロセスの tty 出力


       シグナル SIGKILL と SIGSTOP はキャッチ、ブロック、無視できない。

       次に、 POSIX.1-1990 標準にはないが、 SUSv2 と POSIX.1-2001 に記述されて
       いるシグナルを示す。

       シグナル       値      動作   コメント
       -----------------------------------------------------------------
       SIGBUS      10,7,10    Core   バスエラー (不正なメモリアクセス)
       SIGPOLL                Term   ポーリング可能なイベント (Sys V)。
                                     SIGIOと同義
       SIGPROF     27,27,29   Term   profiling タイマの時間切れ
       SIGSYS      12,-,12    Core   ルーチンへの引数が不正 (SVr4)
       SIGTRAP        5       Core   トレース/ブレークポイント トラップ
       SIGURG      16,23,21   Ign    ソケットの緊急事態 (urgent  condi-
                                     tion) (4.2BSD)
       SIGVTALRM   26,26,28   Term   仮想アラームクロック (4.2BSD)
       SIGXCPU     24,24,30   Core   CPU時間制限超過 (4.2BSD)
       SIGXFSZ     25,25,31   Core   ファイルサイズ制限の超過 (4.2BSD)

      First the signals described in the original POSIX.1-1990 standard.

       Signal     Value     Action   Comment
       -------------------------------------------------------------------------
       SIGHUP        1       Term    Hangup detected on controlling terminal
                                     or death of controlling process
       SIGINT        2       Term    Interrupt from keyboard
       SIGQUIT       3       Core    Quit from keyboard
       SIGILL        4       Core    Illegal Instruction
       SIGABRT       6       Core    Abort signal from abort(3)
       SIGFPE        8       Core    Floating point exception
       SIGKILL       9       Term    Kill signal
       SIGSEGV      11       Core    Invalid memory reference
       SIGPIPE      13       Term    Broken pipe: write to pipe with no readers
       SIGALRM      14       Term    Timer signal from alarm(2)
       SIGTERM      15       Term    Termination signal
       SIGUSR1   30,10,16    Term    User-defined signal 1
       SIGUSR2   31,12,17    Term    User-defined signal 2
       SIGCHLD   20,17,18    Ign     Child stopped or terminated
       SIGCONT   19,18,25    Cont    Continue if stopped
       SIGSTOP   17,19,23    Stop    Stop process
       SIGTSTP   18,20,24    Stop    Stop typed at tty
       SIGTTIN   21,21,26    Stop    tty input for background process
       SIGTTOU   22,22,27    Stop    tty output for background process

       The  signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.

       Next the signals not in the  POSIX.1-1990  standard  but  described  in
       SUSv2 and POSIX.1-2001.

       Signal       Value     Action   Comment
       -------------------------------------------------------------------------
       SIGBUS      10,7,10     Core    Bus error (bad memory access)
       SIGPOLL                 Term    Pollable event (Sys V). Synonym of SIGIO
       SIGPROF     27,27,29    Term    Profiling timer expired
       SIGSYS      12,-,12     Core    Bad argument to routine (SVr4)
       SIGTRAP        5        Core    Trace/breakpoint trap
       SIGURG      16,23,21    Ign     Urgent condition on socket (4.2BSD)
       SIGVTALRM   26,26,28    Term    Virtual alarm clock (4.2BSD)
       SIGXCPU     24,24,30    Core    CPU time limit exceeded (4.2BSD)
       SIGXFSZ     25,25,31    Core    File size limit exceeded (4.2BSD)

 
Processes can catch many of the signals that are sent to them, resulting in a particular signal handler being run. Such a signal handler may, for example, reread a configuration file (this is a common desire, and is usually triggered via the SIGHUP signal). Signals that are not caught generally result in the process exiting.

Summary

We will see all of these topics again when we discuss virtual memory and process scheduling. We will also discuss them in the context of parallel processing.

Parallel Programming Tools

We will work with examples in OpenMP and pthreads.

Programming Parallel Systems

First, "hello, world" in a POSIX threads (pthreads) way. The source code is here. This example is from the Wikipedia page on POSIX threads.

Second, updating a shared counter in a POSIX threads (pthreads) program. The source code is here.

The OpenMP "hello, world" example using multiple threads:

Rodney-Van-Meters-MacBook-Pro:particles rdv$ gcc -fopenmp -o openmp-hello openmp-hello.c
Rodney-Van-Meters-MacBook-Pro:particles rdv$ ./openmp-hello 10
hello(1) hello(2) hello(5) hello(7) hello(0) hello(3) hello(4)
hello(6) hello(8) hello(9) world(1) world(2) world(5) world(7) 
world(0) world(3) world(4) world(6) world(8) world(9) 

An example of "reduction" in OpenMP:

Rodney-Van-Meters-MacBook-Pro:particles rdv$ ./openmp-reduction 
thread 0 added in i=0, total now 0.000000
thread 1 added in i=25, total now 25.000000
thread 0 added in i=1, total now 1.000000
thread 1 added in i=26, total now 51.000000
thread 0 added in i=2, total now 3.000000
thread 1 added in i=27, total now 78.000000
thread 0 added in i=3, total now 6.000000
thread 1 added in i=28, total now 106.000000
thread 0 added in i=4, total now 10.000000
thread 1 added in i=29, total now 135.000000
thread 0 added in i=5, total now 15.000000
thread 1 added in i=30, total now 165.000000
thread 0 added in i=6, total now 21.000000
thread 1 added in i=31, total now 196.000000
thread 0 added in i=7, total now 28.000000
thread 1 added in i=32, total now 228.000000
thread 0 added in i=8, total now 36.000000
thread 1 added in i=33, total now 261.000000
thread 0 added in i=9, total now 45.000000
thread 1 added in i=34, total now 295.000000
thread 0 added in i=10, total now 55.000000
thread 1 added in i=35, total now 330.000000
thread 0 added in i=11, total now 66.000000
thread 0 added in i=12, total now 78.000000
thread 0 added in i=13, total now 91.000000
thread 0 added in i=14, total now 105.000000
thread 0 added in i=15, total now 120.000000
thread 0 added in i=16, total now 136.000000
thread 1 added in i=36, total now 366.000000
thread 0 added in i=17, total now 153.000000
thread 1 added in i=37, total now 403.000000
thread 0 added in i=18, total now 171.000000
thread 1 added in i=38, total now 441.000000
thread 0 added in i=19, total now 190.000000
thread 1 added in i=39, total now 480.000000
thread 0 added in i=20, total now 210.000000
thread 1 added in i=40, total now 520.000000
thread 0 added in i=21, total now 231.000000
thread 1 added in i=41, total now 561.000000
thread 0 added in i=22, total now 253.000000
thread 1 added in i=42, total now 603.000000
thread 0 added in i=23, total now 276.000000
thread 1 added in i=43, total now 646.000000
thread 0 added in i=24, total now 300.000000
thread 1 added in i=44, total now 690.000000
thread 1 added in i=45, total now 735.000000
thread 1 added in i=46, total now 781.000000
thread 1 added in i=47, total now 828.000000
thread 1 added in i=48, total now 876.000000
thread 1 added in i=49, total now 925.000000
ave: 24.500000

The Intel VTune Parallel Performance Tools

To get started using Intel(R) VTune(TM) Amplifier XE 2011 Update 7:
- Add the product bin64 (or bin32) directory (located in
/opt/intel/vtune_amplifier_xe_2011) to
your PATH environment variable.
To start the graphical user interface: amplxe-gui
To use the command-line interface: amplxe-cl
- To view a table of getting started documents:
/opt/intel/vtune_amplifier_xe_2011/documentation/en/documentation_amplifier.htm.
To get started using Intel(R) Inspector XE 2011 Update 8:
- Add the product bin64 (or bin32) directory (located in
/opt/intel/inspector_xe_2011) to
your PATH environment variable.
To start the graphical user interface: inspxe-gui
To use the command-line interface: inspxe-cl
- To view a table of getting started documents:
/opt/intel/inspector_xe_2011/documentation/en/documentation_inspector_xe.htm.
To get started using Intel(R) Composer XE 2011 Update 9 for Linux*:
- Set the environment variables for a terminal window using one of the
following (replace "intel64" with "ia32" if you are using a 32-bit
platform).
For csh/tcsh:
$ source /opt/intel/bin/compilervars.csh intel64
For bash:
$ source /opt/intel/bin/compilervars.sh intel64
To invoke the installed compilers:
For C++: icpc
For C: icc
For Fortran: ifort
To get help, append the -help option or precede with the man command.
- To view a table of getting started documents:
/opt/intel/composer_xe_2011_sp1/Documentation/en_US/documentation_c.htm.

To view movies and additional training, visit
http://www.intel.com/software/products.

Homework

The goal is to understand how the number of processes in the system affects its behavior, and simple principles of parallel programming. Do not forget to include your source code! プロセスの数はシステムに どんな影響するのか、理解することは今週の課題の目標。必ず自分のソース コードもアップ!

Execute your programs on armstrong.sfc.wide.ad.jp

. Contact the TA to get an account on the machine.

This week's homework (due two weeks from today):

  1. This week we have talked about processes. Write a program to call fork() recursively. Eventually, the call will fail in one of the children. When it does, print out the depth of the process tree: how many times have you succeeded in calling fork()? What was the reason for the failure (use perror())?
    今週の テーマはプロセスでした。fork()を永遠まで繰り返す(再帰呼出しで) プログラムを書いてください。いつか、fork() は失敗する。何回の繰 り返し(プロセスツリーの深さ)で失敗する?なぜ失敗する? perror()を使って理由を調べてください。
  2. Now modify that program to collect timing information for a given number of processes, giving the number of processes as an argument to the program (no argument should do the same as above, run until the fork fails). How long does it take to create and delete a thousand processes? Two thousand?
    タイミングの情報も取ってください。1000プロセスの作ると終了するのは、ど のぐらいかかる?2000? 測定してください。
  3. Above, I gave you a multi-threaded program using pthreads that updates a counter in a global shared memory location. It's busted, there is no synchronization and the counter is not updated properly. Fix it.
  4. All of these problems involve variants of the particles program, available on the Berkeley Parallel Bootcamp exercise page. For each problem, execute for n = 500, 1000, 2000 particles. Plot the execution time. You should execute each value five times and report the mean and standard deviation. The simplest option is probably to do the work on ccx00.sfc.keio.ac.jp and use the OpenMP version of the program, but you can do the last exercise with either pthreads or MPI, if you want, and you can use any machine(s) where you have the proper tools available.
    1. First, the serial version.
    2. Second, the existing version of the pthreads program.
      1. First, for -p 1 (one thread). Compare to the serial version.
      2. Next, for 2, 3, 4, 6, 8, and 16 threads.
    3. Third, the existing version of the OpenMP program.
      1. First, for one thread. Compare to the serial version. (You may have to modify the code to allow you to select the number of threads.)
      2. Next, for 2, 3, 4, 6, 8, and 16 threads.
    4. Pick one of the parallel programs: pthreads, OpenMP, or MPI. Solve the problem stated at the Berkeley Parallel Bootcamp exercise page:
      The existing programs all perform poorly because too much information gets shared around; each per-particle loop looks at all of the other particles, which is unnecessary. Your job is to make the program scale better with the number of particles and processes or threads, by reducing the number of particles that each one examines.

Next Lecture

Next lecture:

No class next week! Next class is 5/7.

第4回 5月7日 プロセス間通信
Lecture 4, May 7: Inter-process Communication We will also cover synchronization and introduce deadlock.

Readings for next week:

The following week we will discuss process scheduling, including multiprocessor scheduling and the idea of processor affinity for threads and processes.

Ken Thompson, Butler Lampson, Jim Gray, Nikalus Wirth, from Berkeley

その他 Additional Information