Linux系统编程序学习心得.4

1.信号处理方式

信号处理方式

一个进程收到一个信号的时候,可以用如下方法进行处理:

1)执行系统默认动作

对大多数信号来说,系统默认动作是用来终止该进程。

2)忽略此信号(丢弃)

接收到此信号后没有任何动作。

3)执行自定义信号处理函数(捕获)

用用户定义的信号处理函数处理该信号。SIGKILL 和 SIGSTOP 不能更改信号的处理方式因为它们向用户提供了一种使进程终止的可靠方法。

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

功能:检查或修改指定信号的设置(或同时执行这两种操作)。信号捕捉

struct sigaction {

    void(*sa_handler)(int); //旧的信号处理函数指针    SIG_IGN:忽略该信号,SIG_DFL:执行系统默认动作

    void(*sa_sigaction)(int, siginfo_t *, void *); //新的信号处理函数指针

    sigset_t   sa_mask;      //信号阻塞集

    int        sa_flags;     //信号处理的方式,通常为0

    void(*sa_restorer)(void); //已弃用

};

void(*sa_sigaction)(int signum, siginfo_t *info, void *context);

参数说明:signum:信号的编号。info:记录信号发送进程信息的结构体。 context:可以赋给指向 ucontext_t 类型的一个对象的指针,以引用在传递信号时被中断的接收进程或线程的上下文。

2.函数不安全状态(不可重入)

满足下列条件的函数多数是不可重入(不安全)的:

函数体内使用了静态的数据结构

函数体内调用了malloc() 或者 free() 函数(谨慎使用堆);

函数体内调用了标准 I/O 函数

保证函数的可重入性的方法:

在写函数时候尽量使用局部变量(例如寄存器、栈中的变量);

对于要使用的全局变量要加以保护(如采取关中断、信号量等互斥方法),这样构成的函数就一定是一个可重入的函数。信号处理函数应该为可重入函数

3.SIGCHLD信号

SIGCHLD信号产生的条件

1) 子进程终止时

2) 子进程接收到SIGSTOP信号停止时

3) 子进程处在停止态,接受到SIGCONT后唤醒时

 4.避免僵尸进程

如何避免僵尸进程

最简单的方法,父进程通过 wait() 和 waitpid() 等函数等待子进程结束,但是,这会导致父进程挂起,阻塞

如果父进程要处理的事情很多,不能够挂起,通过 signal() 函数人为处理信号 SIGCHLD , 只要有子进程退出自动调用指定好的回调函数,因为子进程结束后, 父进程会收到该信号 SIGCHLD 可以在其回调函数里调用 wait() 或 waitpid() 回收

如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCHLD, SIG_IGN)通知内核,自己对子进程的结束不感兴趣,父进程忽略此信号,那么子进程结束后,内核会回收, 并不再给父进程发送信号。

会话是一个或多个进程组的集合。

5.守护进程

守护进程(Daemon Process),也就是通常说的 Daemon 进程(精灵进程),是 Linux 中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。一般采用以d结尾的名字。

守护进程是个特殊的孤儿进程,这种进程脱离终端,为什么要脱离终端呢?之所以脱离于终端是为了避免进程被任何终端所产生的信息所打断,其在执行过程中的信息也不在任何终端上显示。由于在 Linux 中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端被关闭时,相应的进程都会自动关闭

Linux 的大多数服务器就是用守护进程实现的。比如,Internet 服务器 inetd,Web 服务器 httpd 等。

守护进程模型

1) 创建子进程,父进程退出(必须)

所有工作在子进程中进行形式上脱离了控制终端

2) 在子进程中创建新会话(必须)

setsid()函数

使子进程完全独立出来,脱离控制

3) 改变当前目录为根目录(不是必须)

chdir()函数

防止占用可卸载的文件系统

也可以换成其它路径

4) 重设文件权限掩码(不是必须)

umask()函数

防止继承的文件创建屏蔽字拒绝某些权限

增加守护进程灵活性

5) 关闭文件描述符(不是必须)

继承的打开文件不会用到,浪费系统资源,无法卸载

6) 开始执行守护进程核心工作(必须)

守护进程退出处理程序模型

孤儿进程没有脱离控制终端,创建新的会话,完全脱离控制终端,setsid,终端退出也不影响当前进程,umask设置权限掩码,tail -f 目录 动态查看文件内容

6.线程 

线程是轻量级的进程(LWP:light weight process),在Linux环境下线程的本质仍是进程。克隆一个函数在底层,如果深拷贝进程,浅拷贝是线程。为了让进程完成一定的工作,进程必须至少包含一个线程进程是CPU分配资源的最小单位。线程是cpu调度的最小单位线程自己基本上不拥有系统资源只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源

线程是轻量级进程(light-weight process),也有PCB,创建线程使用的底层函数和进程一样,都是clone

内核里看进程和线程是一样的,都有各自不同的PCB.

进程可以蜕变成线程

在linux下,线程最是小的执行单位;进程是最小的分配资源单位

实际上,无论是创建进程的fork,还是创建线程的pthread_create底层实现都是调用同一个内核函数 clone 。

如果复制对方的地址空间,那么就产出一个“进程”;如果共享对方的地址空间,就产生一个“线程”

Linux内核是不区分进程和线程的, 只在用户层面上进行区分。所以,线程所有操作函数 pthread_* 是库函数,而非系统调用

7.线程的资源和优缺点

线程共享资源

1) 文件描述符表

2) 每种信号的处理方式

3) 当前工作目录

4) 用户ID和组ID

内存地址空间 (.text/.data/.bss/heap/共享库)(数据段,代码段,bss段,堆区)

线程非共享资源

1) 线程id

2) 处理器现场和栈指针(内核栈)

3) 独立的栈空间(用户空间栈)

4) errno变量

5) 信号屏蔽字

6) 调度优先级

线程的优缺点

优点:

提高程序并发性

开销小

调试、编写困难、gdb不支持

 对信号支持不好

数据通信、共享数据方便

缺点:

库函数,不稳定

优点相对突出,缺点均不是硬伤。Linux下由于实现方法导致进程、线程差别不是很大。

 8.线程号

就像每个进程都有一个进程号一样,每个线程也有一个线程号进程号在整个系统中是唯一的,但线程号不同,线程号只在它所属的进程环境中有效

进程号用 pid_t 数据类型表示,是一个非负整数线程号则用 pthread_t 数据类型来表示,Linux 使用无符号长整数表示。

有的系统在实现pthread_t 的时候,用一个结构体来表示,所以在可移植的操作系统实现不能把它做为整数处理。

pthread_t pthread_self(void);功能:获取线程号

int pthread_equal(pthread_t t1, pthread_t t2);

功能:判断线程号 t1 和 t2 是否相等。为了方便移植,尽量使用函数来比较线程 ID。

参数:t1,t2:待判断的线程号。

9.线程的相关操作函数

int pthread_create(pthread_t *thread,

            const pthread_attr_t *attr,

            void *(*start_routine)(void *),

            void *arg );

功能: 创建一个线程

参数:

    thread:线程标识符地址

    attr:线程属性结构体地址,通常设置为 NULL。

    start_routine:线程函数的入口地址。

    arg:传给线程函数的参数。

一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针start_routine决定

由于pthread_create的错误码不保存在errno中,因此不能直接用perror()打印错误信息,可以先用strerror()把错误码转换成错误信息再打印

int pthread_join(pthread_t thread, void **retval);

功能:等待线程结束(此函数会阻塞)并回收线程资源,类似进程的 wait() 函数。如果线程已经结束,那么该函数会立即返回。

线程分离

一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。

不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL错误。也就是说,如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了

int pthread_detach(pthread_t thread);

功能:使调用线程与当前进程分离,分离后不代表此线程不依赖与当前进程,线程分离的目的是将线程资源的回收工作交由系统自动来完成,也就是说当被分离的线程结束之后,系统会自动回收它的资源。所以,此函数不会阻塞。

void pthread_exit(void *retval);

功能:退出调用线程一个进程中的多个线程是共享该进程的数据段,因此,通常线程退出后所占用的资源并不会释放

线程退出

在进程中我们可以调用exit函数或_exit函数来结束进程,在一个线程中我们可以通过以下三种在不终止整个进程的情况下停止它的控制流。

线程从执行函数中返回。

线程调用pthread_exit退出线程。

线程可以被同一进程中的其它线程取消。

int pthread_cancel(pthread_t thread);

功能:杀死(取消)线程

线程的取消并不是实时的,而又一定的延时。需要等待线程到达某个取消点(检查点)。取消点:是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用creat,open,pause,close,read,write..... 执行命令man 7 pthreads可以查看具备这些取消点的系统调用列表。可粗略认为一个系统调用(进入内核)即为一个取消点。thread_cancel同一个进程的线程。

10.线程属性与注意事项

线程属性:主要结构体成员:

1) 线程分离状态

2) 线程栈大小(默认平均分配)

3) 线程栈警戒缓冲区大小(位于栈末尾)

4) 线程栈最低地址

线程使用注意事项

1) 主线程退出其他线程不退出,主线程应调用pthread_exit

2) 避免僵尸线程

a) pthread_join

b) pthread_detach

c) pthread_create指定分离属性

被join线程可能在join函数返回前就释放完自己的所有内存资源,所以不应当返回被回收线程栈中的值;

3) malloc和mmap申请的内存可以被其他线程释放

4) 应避免在多线程模型中调用fork,除非马上exec,子进程中只有调用fork的线程存在,其他线程t在子进程中均pthread_exit

5) 信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制

线程和进程在内核区分不了,只有在应用的上层空间区分

 总结:

了解信号的处理方式,丢弃,执行,捕获,sigaction函数设定信号,要知道如何去设置这个函数。

函数不可重入状态(不安全状态):使用了静态数据结构,调用了malloc函数和free函数,使用了IO函数,避免这个操作,使用局部变量,全局变量要保护。

SIGCHLD信号是为了避免僵尸进程设定的

当子进程完成操作后,发出信号SIGCHLD,父进程就会调用wait函数去回收子进程的资源,避免僵尸进程,也可设置让内核去自动回收

守护进程是特殊的孤儿进程,它脱离控制终端存在,需要了解如何创建守护进程,首先创建子进程父进程退出,创建新会话,改变工作路径,更改权限掩码,关闭文件描述符,开始守护。

线程,进程是cpu分配资源的最小单位,线程是cpu调度的最小单位。深拷贝是进程,浅拷贝是线程,线程可以共享同一进程的资源,栈是自己独有内存空间,

线程部分bss段,数据段,代码段,堆区是共享的,栈区独有,

进程号是pid_t来存储,线程号是pthread_t来存储,pthread_self函数来获取自身的线程号,

线程创建函数pthread_create要知道传入的参数都是什么,线程回收资源函数pthread_join此函数会阻塞,线程分离函数pthread_detach此函数不阻塞系统自动回收资源,线程退出函数pthread_exit但是此函数不会释放资源,线程杀死函数pthread_cancel此函数杀死线程

线程的注意事项退出函数的使用,避免僵尸线程注意回收资源,堆区申请的资源可以由其他线程释放,避免线程调用fork函数,内核无法区分线程与进程,应用层可以区分。

你可能感兴趣的:(算法,c++,linux,学习,服务器)