进程时间
-
时钟时间又叫做墙上时钟时间,它是进程运行的时钟总量,其值与系统中同时运行的进程数有关,不过一般在讨论时钟时间的时候都是在系统中没有其他活动进行时度量的。
-
用户cpu时间:就是执行用户指令所用的时间。
-
系统CPU时间: 所谓的系统,我们知道就是在内核中执行的时间,没错滴,就是该进程执行内核程序所经历的时间。
查看一个进程的这三个时间并不难,只要执行命令time 即可,如
time date
//结果:
2020年 10月 01日 星期四 14:32:41 CST
real 0m0.005s
user 0m0.001s
sys 0m0.000s
带缓冲、不带缓冲I/O
带缓冲I/O:标准I/O
不带缓冲I/O:系统调用:read/write
标准I/O是在不带缓冲I/O的系统调用基础上,自己多维护了一个流缓冲,以减少系统调用次数。
- 无缓存IO操作数据流向路径:数据——内核缓冲区——磁盘
- 标准IO操作数据流向路径:数据——流缓冲区——内核缓冲区——磁盘
1.可重入函数、异步信号安全、线程安全
重入即表示重复进入,首先它意味着这个函数可以被中断,其次意味着它除了使用自己栈上的变量以外不依赖于任何环境(包括static),这样的函数就是purecode(纯代码)可重入,可以允许有该函数的多个副本在运行,由于它们使用的是分离的栈,所以不会互相干扰。常见的情况是,程序执行到某个函数foo()时,收到信号,于是暂停目前正在执行的函数,转到信号处理函数,而这个信号处理函数的执行过程中,又恰恰也会进入到刚刚执行的函数foo(),这样便发生了所谓的重入。此时如果foo()能够正确的运行,而且处理完成后,之前暂停的foo()也能够正确运行,则说明它是可重入的。
要确保函数可重入,需满足一下几个条件:
- 不在函数内部使用静态或全局数据 .
- 不返回静态或全局数据,所有数据都由函数的调用者提供。
- 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。(比如对errno的处理)
- 不调用不可重入函数。
APUE 10.6:
在信号处理程序中保证调用安全的函数,是
可重入的
并被称为
异步信号安全的
。
可以被信号处理程序安全调用的函数被称为”异步信号安全”函数。信号就像硬件中断一样,会打断正在执行的指令序列。信号处理函数无法判断捕获到信号的时候,进程在何处运行。如果信号处理函数中的操作与打断的函数的操作相同,而且这个操作中有静态数据结构等,当信号处理函数返回的时候(当然这里讨论的是信号处理函数可以返回),恢复原先的执行序列,可能会导致信号处理函数中的操作覆盖了之前正常操作中的数据。
大多数函数是不可重入的,因为:
- 已知它们使用静态(static)数据结构;
- 它们调用malloc或free;
- 它们是标准I/O函数。标准I/O库很多实现都以不可重入方式使用全局数据结构。
12.5:
如果一个函数在相同的时间点可以被多个线程安全地调用,就称该函数是
线程安全
的。
很多函数并不是线程安全的,因为它们返回的数据存放在静态的内存缓冲区。通过修改接口,要求调用者自己提供缓冲区可以使函数变为线程安全。
如果一个函数对多个线程来说是可重入的,就说这个函数是线程安全的。但这并不能说明对信号处理程序来说是该函数也是可重入的
。比如多线程malloc加锁来保证线程安全,但无法保证可重入。
如果函数对异步信号处理程序的重入是安全的,那么就可以说函数是异步信号安全的。
malloc和free有线程安全和线程不安全两种版本,只要使用了线程相关的函数,在编译后的文件中使用的malloc函数就是线程安全的版本(使用锁来实现线程安全),但仍然是不可重入的。
CSAPP 12.7.2:
有一类重要的线程安全函数,叫做可重入函数,其特点在于:
当它们被多个线程调用时,不会引起任何共享数据。
可重入函数集合是线程安全函数的一个真子集,可重入的函数一定是线程安全的,但线程安全不一定是可重入函数。
总结:
- 判断一个函数是不是可重入函数,在于判断其能否可以被打断,打断后恢复运行能够得到正确的结果。(打断执行的指令序列并不改变函数的数据)
- 判断一个函数是不是线程安全的,在于判断其能否在多个线程同时执行其指令序列的时候,保证每个线程都能够得到正确的结果。
- 如果一个函数对多个线程来说是可重入的,则说这个函数是线程安全的,但这并不能说明对信号处理程序来说该函数也是可重入的。
- 如果函数对异步信号处理程序的重入是安全的,那么就可以说函数是”异步-信号安全”的。
参考:
https://www.cnblogs.com/wolfrickwang/p/3729465.html
2.子进程继承
fork()后,子进程获得父进程数据空间、堆和栈的副本,父进程和子进程共享正文段(只读)。
由于fork之后经常跟随者exec,所以很多实现并不执行数据段、栈和堆的完全复制,而是使用
写时复制
技术。这些区域有父进程和子进程共享,而且内核将它们的访问权限改变为只读。如果父进程和子程序中的任一个试图修改这些区域,则内核只为修改区域的那块内存制作一个副本,通常是虚拟存储系统中的一“页”。
exec只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段。
文件描述符继承
文件描述符表位于内核态,fork和exec后仍然继承,不过可以通过设置文件描述符标志:执行时关闭(close-on-exec标志),在exec后自动关闭文件描述符。
调用dup复制描述符时,引用计数也会+1。
内核对所有打开的文件维护一个系统级别的打开文件描述表(open file description table),简称打开文件表。表中条目称为打开文件描述体(open file description),存储了与一个打开文件相关的全部信息,包括:文件偏移量、访问模式、v-node对象指针。
fork函数
继承
父、子进程区别
exec函数
3.线程、子进程的信号处理
信号处理函数:
- 当一个进程调用fork时,因为子进程在开始时复制父进程的存储映像,信号捕捉函数的地址在子进程中是有意义的,所以子进程继承父进程的信号处理方式。
- 当子进程调用exec后,因为exec运行新的程序后会覆盖从父进程继承来的存储映像,那么信号捕捉函数在新程序中已无意义,所以exec会将原先设置为要捕捉的信号都更改为默认动作。
- 多线程:信号处理函数对所有线程有效。进程中的信号是递送到单个线程的。由硬件故障有关的信号,一般被递送到引起该事件的线程中去,其他的信号则被发送到任意一个线程。
信号掩码有以下规则:
- 创建线程时继承主线程的信号掩码,每个线程可以有自己信号掩码;
- fork出来的子进程会继承父进程的信号掩码,exec后信号掩码保持不变;
- 针对进程发送的信号,会被任意的没有屏蔽该信号的线程接收,注意只有一个线程会随机收到;
- fork之后子进程里pending的信号集初始化为空,exec会保持pending信号集。
线程通常如何处理信号?(APUE 12.8)
- 由主线程设置信号掩码,创建的线程继承该掩码。
-
单独设置一个信号处理线程,调用sigwait函数等待信号,处理想要接收的信号。
sigwait(const sigset_t set, int* signo)
set参数指定了线程等待的信号集,如果其中某个信号在sigwait调用时处于挂起状态,那么sigwait将无阻塞的返回,并使signo指向接收到的信号编号,并在返回前从进程中移除那些处于挂起等待状态的信号(并没有调用信号处理函数)。
线程在调用sigwait之前,必须阻塞那些它正在等待的信号。
第14章
14.8存储映射I/O:mmap
MAP_ANONYMOUS 建立匿名映射。此时会忽略参数fd和offset,映射没有任何文件支持;它的内容初始化为零。
MAP_ANON 等同于MAP_ANONYMOUS。
mmap和shm共享内存区别
mmap(memory-mapped I/O)内存映射I/O是将
磁盘文件
映射到存储空间的一个缓冲区上,于是,当从缓冲区中取数据时,就相当于读文件中的相应字节,存数据就相当于字节自动写入文件。
共享内存(shared memory)允许两个或多个进程共享一个给定的存储区(匿名段)。因为数据不需要复制,所以是最快的一种IPC。shmget()创建或引用一个共享内存段后得到内存段id,进程调用shmat就可以将其连接到进程地址空间中。
mmap函数将文件的若干部分映射至进程地址空间,类似于shmat IPC函数连接一个共享存储段。两者主要区别是:mmap映射的存储段是与文件相关联的,而XSI共享存储段没有。因为mmap用的是磁盘文件,所以比shm慢。两者都适用于
无关进程
内存共享。
两种特殊mmap映射:(APUE P463 465)
要求:相关进程
- 使用设备/dev/zero文件映射,指定MAP_SHARED标志
- 使用匿名存储映射,不需要指定文件,需指定MAP_ANONYMOUS标志,并将文件描述符指定为-1。
中断与系统调用
当阻塞的系统调用被信号中断后,信号处理函数返回时,有可能发生以下的情况:
- 如果信号处理函数是用signal注册的,系统调用会自动重启
-
如果信号处理函数是用sigaction注册的:
- 默认情况下,系统调用不会自动重启,函数将返回失败,同时errno被置为EINTR
- 只有中断信号的SA_RESTART标志有效时,系统调用才会自动重启
服务器进程
显示守护进程:
ps -efj | grep 程序名
查看端口状态(服务器端口状态、IP地址):
netstat -nap | grep 程序名
进程组/会话
进程组: Shell 上的一条命令行形成一个进程组,每个进程属于一个进程组,每个进程组有一个领头进程。进程组的生命周期到组中最后一个进程终止, 或加入其他进程组为止。getpgrp: 获得进程组 id, 即领头进程的 pid,setpgid: 加入进程组和建立新的进程组。进程组分为前台进程组和后台进程组。
会话 :一次登录形成一个会话 ,一个会话可包含多个进程组, 但只能有一个前台进程组。setsid 可建立一个新的会话。而对于会话的首进程,pid_t setsid(void),如果调用此函数的进程不是一个进程组的组长,则此函数会创建一个新会话,该进程变成新会话首进程(session leader)。会话首进程是创建该会话的进程。