linux命令
linux文件流
标准输入 0
标准输出 1
标准错误 2
重定向
管道 (pipe): |
管道可以将一个命令的输出导向另一个命令的输入,从而让两个(或者更多命令)像流水线一样连续工作,不断地处理文本流。
进程管理
ps 查看
进程创建:kernel并不提供直接建立新进程的系统调用。剩下的所有进程都是init进程通过fork机制建立的。新的进程要通过老的进程复制自身得到,这就是fork。fork通常作为一个函数被调用。这个函数会有两次返回,将子进程的PID返回给父进程,0返回给子进程。所有进程的PPID父进程都是指向init进程。
pstree 显示init进程树
进程关系
进程组
每个进程都会属于一个进程组(process group),每个进程组中可以包含多个进程。进程组会有一个进程组领导进程 (process group leader),领导进程的PID ,成为进程组的ID (process group ID, PGID),以识别进程组。领导进程可以先终结。此时进程组依然存在,并持有相同的PGID,直到进程组中最后一个进程终结。
会话
在shell支持工作控制(job control)的前提下,多个进程组还可以构成一个会话 (session)。bash(Bourne-Again shell)支持工作控制,而sh(Bourne shell)并不支持。
会话是由其中的进程建立的,该进程叫做会话的领导进程(session leader)。会话领导进程的PID成为识别会话的SID(session ID)。会话中的每个进程组称为一个工作(job)。会话可以有一个进程组成为会话的前台工作(foreground),而其他的进程组是后台工作(background)。每个会话可以连接一个控制终端(control terminal)。当控制终端有输入输出时,都传递给该会话的前台进程组。由终端产生的信号,比如CTRL+Z, CTRL+\,会传递到前台进程组。
会话的意义在于将多个工作囊括在一个终端,并取其中的一个工作作为前台,来直接接收该终端的输入输出以及终端信号。 其他工作在后台运行。一个命令可以通过在末尾加上&方式让它在后台运行。
信号操作
man 7 signal 查看信号
信号处理:无视、默认、自定义
用户权限
每个进程会维护有如下6个ID:
真实身份: real UID, real GID
有效身份: effective UID, effective GID
存储身份:saved UID, saved GID
其中,真实身份是我们登录使用的身份,有效身份是当该进程真正去操作文件时所检查的身份
linux最小权限原则
为了防止其他的用户身份被滥用,我们需要在操作之前,让进程的有效身份变更回来成为真实身份。这样,进程需要在两个身份之间变化。
存储身份就是真实身份之外的另一个身份。当我们将一个程序文件执行成为进程的时候,该程序文件的拥有者(owner)和拥有组(owner group)可以被,存储成为进程的存储身份。在随后进程的运行过程中,进程就将可以选择将真实身份或者存储身份复制到有效身份,以拥有真实身份或者存储身份的权限。并不是所有的程序文件在执行的过程都设置存储身份的。需要这么做的程序文件会在其九位(bit)权限的执行位的x改为s。这时,这一位(bit)叫做set UID bit或者set GID bit。
$ls -l /usr/bin/uuidd
-rwsr-sr-x 1 libuuid libuuid 17976 Mar 30 2012 /usr/sbin/uuidd
当我以root(UID), root(GID)的真实身份运行这个程序的时候,由于拥有者(owner)有s位的设定,所以saved UID被设置成为libuuid,saved GID被设置成为libuuid。这样,uuidd的进程就可以在两个身份之间切换。
我们通常使用chmod来修改set-UID bit和set-GID bit:
$chmod 4700 file
我们看到,这里的chmod后面不再只是三位的数字。最前面一位用于处理set-UID bit/set-GID bit,它可以被设置成为4/2/1以及或者上面数字的和。4表示为set UID bit, 2表示为set GID bit,1表示为sticky bit (暂时不介绍)。必须要先有x位的基础上,才能设置s位。
fork与exec区别
当一个程序调用fork的时候,实际上就是将上面的内存空间,包括text, global data, heap和stack,又复制出来一个,构成一个新的进程,并在内核中为改进程创建新的附加信息 (比如新的PID,而PPID为原进程的PID)。此后,两个进程分别地继续运行下去。新的进程和原有进程有相同的运行状态(相同的变量值,相同的instructions...)。我们只能通过进程的附加信息来区分两者。
程序调用exec的时候,进程清空自身内存空间的text, global data, heap和stack,并根据新的程序文件重建text, global data, heap和stack (此时heap和stack大小都为0),并开始运行。
多线程
多线程就是允许一个进程内存在多个控制权,以便让多个函数同时处于激活状态,从而让多个函数的操作同时运行。即使是单CPU的计算机,也可以通过不停地在不同线程的指令间切换,从而造成多线程同时运行的效果。
一个栈,只有最下方的帧可被读写。相应的,也只有该帧对应的那个函数被激活,处于工作状态。为了实现多线程,我们必须绕开栈的限制。为此,创建一个新的线程时,我们为这个线程建一个新的栈。每个栈对应一个线程。当某个栈执行到全部弹出时,对应线程完成任务,并收工。所以,多线程的进程在内存中有多个栈。多个栈之间以一定的空白区域隔开,以备栈的增长。每个线程可调用自己栈最下方的帧中的参数和变量,并与其它线程共享内存中的Text,heap和global data区域。
并发与竞争
最常见的解决竞争条件的方法是将原先分离的两个指令构成不可分隔的一个原子操作(atomic operation),而其它任务不能插入到原子操作中。
多线程同步
互斥锁(mutex)
互斥锁是一个特殊的变量,它有锁上(lock)和打开(unlock)两个状态。互斥锁一般被设置成全局变量。打开的互斥锁可以由某个线程获得。一旦获得,这个互斥锁会锁上,此后只有该线程有权打开。其它想要获得互斥锁的线程,会等待直到互斥锁再次打开的时候。
/*mu is a global mutex*/
while (1) { /*infinite loop*/
mutex_lock(mu); /*aquire mutex and lock it, if cannot, wait until mutex is unblocked*/
if (i != 0) i = i - 1;
else {
printf("no more tickets");
exit();
}
mutex_unlock(mu); /*release mutex, make it unblocked*/
}
条件变量(condition variable)
条件变量特别适用于多个线程等待某个条件的发生。
/*mu: global mutex, cond: global codition variable, num: global int*/
mutex_lock(mu)num = num + 1; /*worker build the room*/
if (num <= 10) { /*worker is within the first 10 to finish*/
cond_wait(mu, cond); /*wait*/
printf("drink beer");
}
else if (num = 11) { /*workder is the 11th to finish*/
cond_broadcast(mu, cond); /*inform the other 9 to wake up*/
}mutex_unlock(mu);
读写锁(reader-writer lock)
共享读取锁(shared-read), 互斥写入锁(exclusive-write lock), 打开(unlock)。
进程间通信
- 管道(PIPE)机制。在Linux文本流中,我们提到可以使用管道将一个进程的输出和另一个进程的输入连接起来,从而利用文件操作API来管理进程间通信。在shell中,我们经常利用管道将多个进程连接在一起,从而让各个进程协作,实现复杂的功能。
- 传统IPC (interprocess communication)。我们主要是指消息队列(message queue),信号量(semaphore),共享内存(shared memory)。这些IPC的特点是允许多进程之间共享资源,这与多线程共享heap和global data相类似。由于多进程任务具有并发性 (每个进程包含一个进程,多个进程的话就有多个线程),所以在共享资源的时候也必须解决同步的问题 。
- 信号量semaphore与mutex类似,用于处理同步问题。我们说mutex像是一个只能容纳一个人的洗手间,那么semaphore就像是一个能容纳N个人的洗手间。其实从意义上来说,semaphore就是一个计数锁(我觉得将semaphore翻译成为信号量非常容易让人混淆semaphore与signal),它允许被N个进程获得。当有更多的进程尝试获得semaphore的时候,就必须等待有前面的进程释放锁。当N等于1的时候,semaphore与mutex实现的功能就完全相同。许多编程语言也使用semaphore处理多线程同步的问题。一个semaphore会一直存在在内核中,直到某个进程删除它。
- 共享内存与多线程共享global data和heap类似。一个进程可以将自己内存空间中的一部分拿出来,允许其它进程读写。当使用共享内存的时候,我们要注意同步的问题。我们可以使用semaphore同步,也可以在共享内存中建立mutex或其它的线程同步变量来同步。由于共享内存允许多个进程直接对同一个内存区域直接操作,所以它是效率最高的IPC方式。
- 消息队列(message queue)与PIPE相类似。它也是建立一个队列,先放入队列的消息被最先取出。不同的是,消息队列允许多个进程放入消息,也允许多个进程取出消息。每个消息可以带有一个整数识别符(message_type)。你可以通过识别符对消息分类 (极端的情况是将每个消息设置一个不同的识别符)。某个进程从队列中取出消息的时候,可以按照先进先出的顺序取出,也可以只取出符合某个识别符的消息(有多个这样的消息时,同样按照先进先出的顺序取出)。消息队列与PIPE的另一个不同在于它并不使用文件API。最后,一个队列不会自动消失,它会一直存在于内核中,直到某个进程删除该队列。
- 共享内存(shared memory)。