系统调用
open
函数
文件打开函数
函数原型:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode)
返回值为一个文件描述符
参数列表:
-
pathname
:文件的完整路径 -
flags
:打开文件的模式,
常用的模式包括:O_WRONLY
:只写模式O_RDONLY
:只读模式O_RDWR
:读写模式O_CREAT
:如果文件不存在则创建O_EXCL
:如果文件不存在则返回错误码O_TRUNC
:文件截断为0 (清空)O_NONBLOCKING
:设置为非阻塞状态
-
mode
:文件权限,在创建文件时生效,文件最终权限为mode - umask
-
如果没有指定权限,则默认权限为777-
umaks
-
umask
默认是三个八进制数字
return value
:成功则返回文件描述符,失败则返回-1
open常见错误:
- 文件不存在:此时
fd
为-1,errno
=2 - 权限不足:
fd
=-1,errno
=13 - 以写方式打开目录:
fd
=-1,errno
=21
错误打印:printf("%s\n",strerror(errno))
也可以使用perror
,它会自动和errno结合,输出你设置的描述和errno
对应的错误描述
read、write
函数
文件的读、写操作
函数原型:
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
参数列表:
-
fd
:文件描述符 -
buf
:数据缓冲区 -
count
:(read)缓冲区大小 (write)写入数据的大小
read
返回值:
- 成功 返回读取、写入的字节数
- 失败 返回-1且
errno
设置为对应的值 - -1 并且
errno=EAGAIN
或EWOULDBLOCK
说明不是read
失败,而是read
在以非阻塞方式读一个设备或者网络文件,并且文件无数据 - 0 读到文件末尾
read
和write
函数通常被成为Unbuffered I/O
。指的是无用户级缓冲,但不保证不适用内核缓冲区
文件的读和写都是共用的一个偏移量
文件描述符
PCB进程控制块: 一个结构体
成员包含文件描述符表
文件描述符:0、1、2……1023
新打开的文件描述符时表中可用的最小的
- 0 -
STDIN_FILENO
- 1 -
STDOUT_FILENO
- 2 -
STDERR
struct file{
...
偏移量
访问权限
打开标志
内核缓冲区首地址
struct operations *f_op;
};
阻塞、非阻塞:
阻塞是设备、网络文件的属性
产生阻塞的场景:
- 读设备文件
- 网络文件(常规文件无阻塞这个概念)
/dev/tty
-> 终端文件
fcntl
函数
用于文件控制
函数原型:
int fcntl(int fd, int cmd, ... /* arg */ );
参数列表:
fd
:对应的文件描述符cmd
:控制指令F_GETFL
:获取文件状态F_SETFL
:设置文件状态
...
:相应指令对应的参数
返回值:
- 成功,返回命令对应的返回值
- 失败,返回
-1
lseek
函数
用于将文件指针移动到指定偏移量处
函数原型:
off_t lseek(int fd, off_t offset, int whence)
参数列表:
fd
:文件描述符offset
:偏移量whence
:偏移位置,常见值有SEEK_SET
SEEK_CUR
SEEK_END
SEEK_SET
:设置偏移字节SEEK_CUR
:偏移量设置为当前位置+偏移字节SEEK_END
:偏移量设置为文件大小+偏移字节
返回值:
- 成功:起始位置偏移量
- 失败:-1,
errno
设置为相应值
可以用于文件扩展,获取文件大小
文件存储
首先了解文件存储的相关概念:
inode
dentry
存储系统 文件系统
inode
本质上是一个结构体,存储文件的属性。比如:权限,类型,大小,时间,用户,位置……也叫做文件属性管理结构,大多数inode
都存储在磁盘上
dentry
目录项,本质是结构体,重要的成员变量有两个:文件名和inode
,文件内容保存在磁盘盘块中
相关系统调用
stat
函数
用于获取文件属性
函数原型:
int stat(const char *pathname, struct stat *statbuf)
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* Inode number */
mode_t st_mode; /* File type and mode */
nlink_t st_nlink; /* Number of hard links */
uid_t st_uid; /* User ID of owner */
gid_t st_gid; /* Group ID of owner */
dev_t st_rdev; /* Device ID (if special file) */
off_t st_size; /* Total size, in bytes */
blksize_t st_blksize; /* Block size for filesystem I/O */
blkcnt_t st_blocks; /* Number of 512B blocks allocated */
struct timespec st_atim; /* Time of last access */
struct timespec st_mtim; /* Time of last modification */
struct timespec st_ctim; /* Time of last status change */
};
参数列表:
pathname
:文件路径和文件名statbuf
:(传出参数)文件属性缓冲区指针
返回值:- 成功:
0
- 失败:
-1
,errno
设置为对应的值
stat
函数是默认可以穿透符号链接文件的,当查询符号链接文件信息时,默认获取链接所指向文件的文件信息。如果不想穿透,则可以使用lstat
函数,这两个函数的用法是一样的。
int stat_test(const char *path) {
struct stat file_info;
int ret = lstat(path, &file_info);
if (ret != 0) {
perror("stat error");
_exit(-1);
}
printf("file size=%ld\n", file_info.st_size);
switch (file_info.st_mode & S_IFMT) {
case S_IFBLK: printf("block device\n");
break;
case S_IFCHR: printf("character device\n");
break;
case S_IFDIR: printf("directory\n");
break;
case S_IFIFO: printf("FIFO/pipe\n");
break;
case S_IFLNK: printf("symlink\n");
break;
case S_IFREG: printf("regular file\n");
break;
case S_IFSOCK: printf("socket\n");
break;
default: printf("unknown?\n");
break;
}
return 0;
}
link
函数
用于创建目录项
函数原型:
int link(const char *oldpath, const char *newpath)
参数列表:
oldpath
:旧目录项newpath
:新目录项
readlink
函数
读取符号链接文件本身内容,得到链接所指向文件名
函数原型:
ssize_t readlink(const char *pathname, char *buf, size_t bufsiz)
参数列表:
pathname
:文件链接路径buf
:所指向的文件名bufsiz
:接收缓冲区大小
目录操作函数
对目录进行操作
函数原型:
DIR *opendir(const char *name);
struct dirent *readdir(DIR *dirp);
int closedir(DIR *dirp);
char *getcwd(char *buf, size_t size);
结构体内部:
struct dirent {
ino_t d_ino; /* Inode number */
off_t d_off; /* Not an offset; see below */
unsigned short d_reclen; /* Length of this record */
unsigned char d_type; /* Type of file; not supported
by all filesystem types */
char d_name[256]; /* Null-terminated filename */
};
代码用例
#include <dirent.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <string.h>
#include <stdio.h>
void my_ls(const char *dir) {
//参数为空,则为当前工作目录
if (strlen(dir) == 0) {
dir = getcwd(NULL, 0);
}
DIR *dir_p;
struct dirent *pdirent;
dir_p = opendir(dir);
if (dir_p == NULL) {
perror("opendir failed\n");
_exit(-1);
}
//读取目录项
while (1) {
pdirent = readdir(dir_p);
if (pdirent == NULL) {
break;
}
if (strcmp(pdirent->d_name, ".") == 0 || strcmp(pdirent->d_name, "..") == 0) {
continue;
}
printf("%s\t", pdirent->d_name);
};
fprintf(stdout, "\n");
closedir(dir_p);
}
getcwd
获取当前工作目录
函数有两种用法
//1.
char *dir = NULL;
dir = getcwd(NULL, 0);
//2.
char dir[256]={0};
getcwd(dir,sizeof(dir));
重定向
将制定的文件描述符复制一份并返回新的文件描述符
函数原型:
int dup(int oldfd);
int dup2(int oldfd, int newfd);
让newfd
指向oldfd
进程
PCB进程控制块
- 程序:死的,只占用磁盘空间
- 进程:程序的一次运行过程,占用系统资源
进程控制块包含
- 标识符
- 状态(就绪,运行,挂起)
- 切换时需要保存和恢复的CPU寄存器
- 虚拟地址空间信息
- 控制终端信息
- 当前工作目录
umask
掩码- 文件描述符表
- 信号相关信息
- 用户ID和组ID
- 会话和进程组
- 可使用的资源上限
fork
创建一个子进程
函数原型:
pid_t fork(void)
使用fork
函数创建子进程后,子进程从fork
函数之后开始运行,父子进程各自返回,父进程的fork
函数返回子进程的pid
,子进程返回0
相关的函数还有:
pid_t getpid(void)
pid_t getppid(void)
fork
之后
- 相同处
- 全局变量
- .data
- .text
- 堆栈
- 环境变量
- 用户ID
- 宿主目录
- 工作目录
- 信号处理方式……
- 不同处
- 进程ID
fork
返回值- 父进程ID
- 运行时间
- 定时器
父子进程间遵循读时共享写时复制
原则,顾名思义,当需要写全局变量时,就会复制一份然后写复制品
父子进程共享打开文件描述符和mmap
建立的映射区
exec
函数族
fork
函数执行后,父子进程都执行相同的代码,子进程使用exec
函数可以执行另一程序,此时进程用户空间代码和数据完全被新的程序替代,从新的程序启动例程开始执行。exec
函数并不会创建新的进程,前后进程id
并不会发生改变
函数原型:
extern char **environ;
执行一个可执行文件
int execl(const char *pathname, const char *arg, ...);
借助环境变量来执行一个可执行文件
int execlp(const char *file, const char *arg, ...);
int execle(const char *pathname, const char *arg, ...);
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
函数示例:
void exec_test(void) {
printf("%s:%d start function [%s]\n", __FILE__, __LINE__, __FUNCTION__);
printf("main pid %d\n", getpid());
pid_t pid = fork();
if (pid < 0) {
perror("pid<0");
_exit(-1);
}
if (pid == 0) {
//argv[0]是程序本身
execlp("ls", "ls", "-l", "-h", NULL);//NULL代表参数结束
//exec执行失败,就会执行下列代码
perror("exec error");
_exit(1);
} else {
sleep(1);
printf("I'm parent: %d\n", getpid());
}
printf("ending\n");
}
exec函数族一般规律:
一旦执行成功,即开始新的程序,不返回,只有失败才会返回,错误值为-1,所以再调用之后直接使用perror
或者exit
,无需使用判断语句
l(list)
:命令行参数列表p(path)
:搜索file
时使用path
变量v(vector)
:使用命令行参数数组e(environment)
:使用环境变量数组,不适用进程原有的环境变量,设置新加载程序运行的环境变量
孤儿进程
父进程先于子进程结束,则子进程为孤儿进程,此时子进程的父进程变为
init
进程,称为init
进程领养孤儿进程
僵尸进程
进程终止后,父进程尚未回收,子进程残余资源
(PCB)
放于内核,变为僵尸进程
就是子进程死后,父进程还在运行的状态,子进程就变为僵尸进程
wait
函数
进程终止后会释放资源,但是他的
PCB
会保留再内核中,如果正常退出则保留退出状态,异常终止则保留导致终止的信号,此时父进程使用wait
函数可以获取这些信息
函数原型:
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
参数:
wstatus
:一个传出参数,用于获取结果
返回值:
- 成功:回收子进程的
pid
- 失败:-1
wait
只会回收结束了的子进程,如果没有结束,则会阻塞直到结束
获取到的status
可以通过一系列宏函数获取到相关信息
WIFEXITED (status)
:
若子进程正常结束则返回真(true)
。 此时,宏WEXITSTATUS(status)
返回子进程的退出状态。WIFSIGNALED (status)
:
若通过信号杀掉子进程则返回真(true)
。此时,宏WTERMSIG(status)
返回导致子进程终止 的信号编号。若子进程产生内核转储文件,则宏WCOREDUMP(status)
返回真值(true)
。SUSv3
并未规范宏WCOREDUMP()
,不过大部分UNIX
实现均支持该宏WIFSTOPPED (status)
:
若子进程因信号而停止,则此宏返回为真(true)。
此时,宏WSTOPSIG(status)
返回导致子进程停止的信号编号WIFCONTINUED (status)
若子进程收到SIGCONT
而恢复执行,则此宏返回真值(true)
。自Linux 2.6.10
之后开始支持该宏
waitpid
系统调用:
pid
:填写子进程的pid
,为0则表示任意子进程options
参数是一个整型变量,用于指定waitpid
系统调用的行为。常见的选项有WNOHANG
和WUNTRACED
.WNOHANG
:如果没有子进程结束,则立即返回0。如果有子进程结束,则返回该子进程的PID
,非阻塞式回收。WUNTRACED
:如果子进程被暂停,则返回该子进程的PID
。如果子进程没有被暂停,不返回。
返回值
- 成功:子进程
pid
- 失败:-1
进程间通信IPC
常用的有四种方式
- 管道(最简单)
- 信号(数据载量小)
- 共享内存映射(只能用于有关系的进程之间)
- 本地套接字(最稳定但复杂)
- fifo
管道
最基本的
IPC
机制,作用于有血缘关系的进程之间,完成数据传递
原理:实为内核使用环形队列机制,借助内核缓冲区实现,大小为4096字节
- 本质是一个伪文件(实为内核缓冲区)
- 由两个文件描述符引用,一个表示读端,一个表示写端
- 规定数据从管道写端流入,从读端流出
局限性:
- 进程无法自写自读
- 不可重复读取,读走后就不存在
- 单工通信,只能单方向流动
- 只能在有公共祖先的进程间使用
函数原型:
int pipe(int pipefd[2])
形参:
pipefd[2]
:两个文件描述符,一个用于读,一个用于写,fd[1]
用于写,fd[0]
用于读
管道的读写行为:
- 读管道:
- 管道中有数据:
read
返回实际读到的字节数 - 管道中无数据:
- 写端被全部关闭,
read
返回0(相当于读到文件末尾) - 写端没有被全部关闭,
read
阻塞等待
- 写端被全部关闭,
- 管道中有数据:
- 写管道:
- 读端被全部关闭:进程异常终止,也可捕捉
SIGPIPE
信号,使进程不终止 - 读端没有全部关闭:
- 管道满:
write
阻塞 - 管道未满:
write
写入成功,返回实际写入字节
- 管道满:
- 读端被全部关闭:进程异常终止,也可捕捉
fifo
命名管道
可以用于两个没有血缘关系的进程通信,用起来和文件差不多
创建一个命名管道
函数原型:
int mkfifo(const char *pathname, mode_t mode);
参数:
pathname
:管道的完整路径mode
:权限,与创建文件时权限一致
返回值:
与大多系统调用一样,成功返回0,失败返回-1
存储区映射I/O
又叫
mmap
,将一个对象或者文件映射进内存
函数原型:
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
int munmap(void *addr, size_t length);
mmap
:创建存储区映射函数
参数:
addr
:映射内存的地址,通常使用NULL
,让系统自己分配length
:共享内存映射区大小(≤文件或对象的大小)prot
:设置共享内存的读写属性。PROT_READ
,PROT_WRITE
flags
:标注共享内存的共享属性。MAP_SHARED
,MAP_PRIVATE
fd
:用于创建共享内存映射区的那个文件的文件描述符offset
:默认0,表示映射文件全部。偏移量,需要为4K的整数倍
返回值:
- 成功:内存映射区的首地址
- 失败:
MAP_FAILED
,并且设置errno
注意事项:
- 用于创建映射区文件大小为0,指定非0大小创建映射区\(\Rightarrow\)总线错误
- 用于创建映射区文件大小为0,指定0大小创建映射区\(\Rightarrow\)参数不合法
- 用于创建映射区文件属性为只读,指定映射区属性为读写\(\Rightarrow\)参数不合法
- 文件描述符在
mmap
创建完成后就可以关闭,后续访问可通过缓冲区映射完成 - 映射最小单位为4K,所以
offset
要为4k整数倍 - 如果映射区访问权限为私有,对内存操作只对内存有效,对磁盘上的文件无效
父子间进程通信:
- 父进程创建共享内存映射区。
open(O_RDWR)
mmap(MAP_SHARED)
- 指定
MAP_SHARED
权限 fork
创建子进程- 一个进程读,一个进程写
无血缘关系进程间使用mmap
通信:
- 两个进程打开同一个文件,创建映射区
- 指定权限为
MAP_SHARED
- 一个进程写入,一个进程读出
操作系统提供的特殊文件
和匿名映射一样,只能用于有血缘关系的进程
/dev/zero
:读取或者写入时,提供无限的数据流/dev/null
:空设备文件或者黑洞文件
信号
信号是信息的载体,系统中通信主要手段
概念
- 未决:信号产生与递达之间状态
- 递达:产生并送达至进程。直接被内核处理
- 信号处理方式:执行默认处理动作、忽略、捕捉(自定义)
- 阻塞信号集(信号屏蔽字):本质:位图。用来记录信号的屏蔽状态。一旦屏蔽的信号,在解除前一致处于未决状态
- 未决信号集:处于未决状态的集合
信号四要素:
- 编号
- 名称
- 事件
- 默认处理动作
Linux常规信号一览表
编号 | 名称 | 意义 | 默认动作 |
---|---|---|---|
1 | SIGHUP |
当用户退出shell 时,由该shell 启动的所有进程将收到这个信号 |
终止进程 |
2 | SIGINT |
当用户按下了<Ctrl+C> 组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号 |
终止进程 |
3 | SIGQUIT |
当用户按下<ctrl+\> 组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出些信号 |
终止进程。 |
4 | SIGILL |
CPU 检测到某进程执行了非法指令 |
终止进程并产生core 文件 |
5 | SIGTRAP |
该信号由断点指令或其他 trap 指令产生 |
终止里程 并产生core 文件。 |
6 | SIGABRT |
调用abort 函数时产生该信号 |
终止进程并产生core 文件。 |
7 | SIGBUS |
非法访问内存地址,包括内存对齐出错 | 终止进程并产生core 文件。 |
8 | SIGFPE |
在发生致命的运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等所有的算法错误 | 终止进程并产生core 文件。 |
9 | SIGKILL |
无条件终止进程。本信号不能被忽略,处理和阻塞 | 终止进程。它向系统管理员提供了可以杀死任何进程的方法。 |
10 | SIGUSR1 |
用户定义 的信号。即程序员可以在程序中定义并使用该信号 | 终止进程。 |
11 | SIGSEGV |
指示进程进行了无效内存访问 | 终止进程并产生core 文件。 |
12 | SIGUSR2 |
另外一个用户自定义信号,程序员可以在程序中定义并使用该信号 | 终止进程。 |
13 | SIGPIPE |
Broken pipe 向一个没有读端的管道写数据 |
终止进程。 |
14 | SIGALRM |
定时器超时,超时的时间 由系统调用alarm 设置 |
终止进程。 |
15 | SIGTERM |
程序结束信号,与SIGKILL 不同的是,该信号可以被阻塞和终止。通常用来要示程序正常退出。执行shell 命令Kill 时,缺省产生这个信号 |
终止进程。 |
16 | SIGSTKFLT |
Linux 早期版本出现的信号,现仍保留向后兼容 |
终止进程。 |
17 | SIGCHLD |
子进程结束时,父进程会收到这个信号 | 忽略这个信号。 |
18 | SIGCONT |
如果进程已停止,则使其继续运行 | 继续/忽略。 |
19 | SIGSTOP |
停止进程的执行。信号不能被忽略,处理和阻塞 | 暂停进程。 |
20 | SIGTSTP |
停止终端交互进程的运行。按下<ctrl+z> 组合键时发出这个信号 |
暂停进程。 |
21 | SIGTTIN |
后台进程读终端控制台 | 暂停进程。 |
22 | SIGTTOU |
该信号类似于SIGTTIN ,在后台进程要向终端输出数据时发生 |
暂停进程。 |
23 | SIGURG |
套接字上有紧急数据时,向当前正在运行的进程发出些信号,报告有紧急数据到达。如网络带外数据到达 | 忽略该信号 |
24 | SIGXCPU |
进程执行时间超过了分配给该进程的CPU 时间 ,系统产生该信号并发送给该进程 |
终止进程。 |
25 | SIGXFSZ |
超过文件的最大长度设置 | 终止进程。 |
26 | SIGVTALRM |
虚拟时钟超时时产生该信号。类似于SIGALRM ,但是该信号只计算该进程占用CPU 的使用时间 |
终止进程。 |
27 | SGIPROF |
类似于SIGVTALRM ,它不公包括该进程占用CPU 时间还包括执行系统调用时间 |
终止进程。 |
28 | SIGWINCH |
窗口变化大小时发出 | 忽略该信号。 |
29 | SIGIO |
此信号向进程指示发出了一个异步IO 事件 |
忽略。 |
30 | SIGPWR |
关机 | 终止进程。 |
31 | SIGSYS |
无效的系统调用 | 终止进程并产生core 文件。 |
32 | SIGRTMIN ~ (64) SIGRTMAX |
LINUX的实时信号,它们没有固定的含义(可以由用户自定义) | 所有的实时信号的默认动作都为终止进程。 |
简单的信号触发函数
函数原型:
int kill(pid_t pid, int sig);
参数列表:
pid
:> 0
:杀死指定进程;= 0
:发送信号给调用kill
函数的那个进程处于同一进程组的所有进程;<-1
:取绝对值发送给对应的进程组;=-1
:发送给进程有权限发送的系统中所有进程
sig
:信号
返回值:
- 成功:0
- 失败:-1
errno
设置为指定值
函数原型:
unsigned int alarm(unsigned int seconds);
定时器,到指定时间后发送
SIGALRM
信号
参数列表:
seconds
:定时的秒数,0为取消闹钟
返回值:
- 上次定时剩余的时长
可以使用setitimer
函数设置更为精细的定时
信号集操作
相关函数原型
sigset_t set
自定义信号集sigemptyset(sigset_t *set)
清空信号集sigfillset(sigset_t *set)
信号集置一sigaddset(sigset_t *set, int signum)
将一个信号添加到集合中sigdelset(sigset_t *set, int signum)
将一个信号从集合中移除sigismember(sigset_t *set, int signum)
判断一个信号是否在集合中
前5个都是成功\(\Rightarrow\) 0,失败\(\Rightarrow\)-1;
第6个在 \(\Rightarrow\) 1,不在 \(\Rightarrow\) 0
设置信号屏蔽字 sigprocmask
函数原型:
将set中的信号从屏蔽字中加入或去掉
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数列表:
how
:SIG_BLOCK
:设置阻塞SIG_UNBLOCK
:取消阻塞SIG_SETMASK
:用自定义set
替换mask
set
:用户自定义的set
oldset
:
查看未决信号集sigpending
int sigpending (sigset_t *set);
set
是一个传出参数,传出的是未决信号集
信号处理 ->signal
函数
设置信号处理事件
函数原型:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
参数列表:
signum
:信号编号handler
:信号处理函数
注册信号处理函数sigaction
函数原型:
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
使用示例
void sigaction_test(void) {
struct sigaction act, oldact;
//将屏蔽字清空
sigemptyset(&(act.sa_mask));//设置屏蔽字,在捕捉函数执行期间有效
act.sa_handler = sig_catch; //注册信号处理函数
act.sa_flags = 0;//设置默认属性,通常为0
int ret = sigaction(SIGINT, &act, &oldact);
if (ret < 0) {
perror("sigaction");
_exit(-1);
}
while (1) {
sleep(1);
}
}
信号特性
- 信号捕捉函数执行期间,自动被屏蔽(
sa_flags=0
)- 正常运行时,进程只有一个信号屏蔽字(假设为A,捕捉到信号后调用处理函数,如果函数执行时间较长,这期间屏蔽信号不由A决定,而是由
sa_mask
指定。- 阻塞的常规信号不支持排队,多次触发只会处理一次(后32个信号支持排队),阻塞期间,如果被阻塞信号多次发生,则只处理一次
守护进程
daemon
进程,运行在操作系统后台,脱离控制终端。一般不与用户直接交互,周期性等待某个事件发生,不受用户登录注销影响。
创建守护进程,最关键的一步就是用setsid
函数创建一个新的session
,并成为session leader
创建守护进程步骤
fork
出子进程,让父进程终止- 子进程调用
setsid()
创建 - 根据需要,改编工作目录
chdir()
- 根据需要,设置
umask
文件权限掩码 - 根据需要,关闭/重定向文件描述符
- 守护进程完成业务逻辑。
while()
线程
概念
线程是调度的最小单位
进程是资源分配的最小单位
优点:开销小;通信简单;提高程序并发性
缺点:库函数,不稳定;调试编写困难,GDB
不支持;对信号支持不友好
线程控制原语
pthread_t pthread_self(void);
获取线程
id
,类似于getpid()
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
返回值描述的是线程创建成功与否
void pthread_exit(void *retval);
将线程退出,除此之外,在线程函数中使用
return
也可以退出线程
int pthread_join(pthread_t thread, void **retval)
阻塞线程等待回收
int pthread_detach(pthread_t thread);
线程分离函数
int pthread_cancel(pthread_t thread);
标签:文件,函数,int,编程,笔记,char,信号,Linux,进程 From: https://www.cnblogs.com/Lhh-9999/p/17541444.html终止线程,但是要借助取消的契机,如使用
pthread_testcancel
函数可添加契机点