内核
内核模块
insmod/modprobe
命令加载运行rmmod
命令卸载进程
线程
struct task_struct
结构体进程间通信
深刻理解Linux进程间通信
Linux IPC总结(全)
为什么要使用虚拟地址?
用户进程,以及大部分的内核线程,都只能看到虚拟地址。内核中的地址映射模块实现了虚拟地址到物理地址之间的映射和转换。
通过虚拟地址访问内存,有如下优势:
程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲区。
程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。当物理内存的供应量变小时,内存管理器会将物理内存页(通常大小为4KB)保存到磁盘文件。数据或代码页会根据需要在物理内存与磁盘之间移动。
不同进程使用的虚拟地址彼此隔离。一个进程中的代码无法更改正在由另一进程或操作系统使用的物理内存。一旦有访问非法地址,地址映射模块就会立即报错。
Linux系统中为什么不适用物理地址而是使用虚拟地址
1、物理地址
物理地址实际上就是硬件设备上实际的存储设备,内存硬件地址,与处理器的地址总线相对应,数据真实访问的位置,一般程序都无法看到物理内存地址。
2、虚拟地址
虚拟地址是Linux内核虚拟出来的地址,经由MMU内存管理单元映射到实际的物理地址。MMU是实际的管理内存的硬件。程序访问内存硬件使用的虚拟内存地址,由操作系统抽象出来的虚拟内存可以使得程序访问比真实物理内存大得多的内存范围,通过内核的地址映射功能(借助硬件MMU)实现。
3、直接使用物理地址
如果直接使用物理地址的话:
(1)安全风险
每个进程都可以访问0-4G的任意的内存空间,这也就意味着任意一个进程都能够去读写系统相关内存区域,如果是一个木马病毒,那么他就能随意的修改内存空间,让设备直接瘫痪
(2)地址不确定
众所周知,编译完成后的程序是存放在硬盘上的,当运行的时候,需要将程序搬到内存当中去运行,如果直接使用物理地址的话,我们无法确定内存现在使用到哪里了,也就是说拷贝的实际内存地址每一次运行都是不确定的,比如:第一次执行a.out时候,内存当中一个进程都没有运行,所以搬移到内存地址是0x00000000,但是第二次的时候,内存已经有10个进程在运行了,那执行a.out的时候,内存地址就不一定了
(3)效率低下
如果直接使用物理内存的话,一个进程就是作为一个整体(内存块)操作的,如果出现物理内存不够用的时候,我们一般的办法是将不常用的进程拷贝到磁盘的交换分区中,好腾出内存,但是如果是物理地址的话,就需要将整个进程一起拷走,这样,在内存和磁盘之间拷贝时间太长,效率较低。
4、虚拟地址实现
虚拟地址实际上就相当于在物理地址和进程间引入一个第三者,一般实现方法有两种:分段映射和分页映射。
分段映射能够解决安全隐患、地址不确定问题,但是对于效率问题仍然没有很好的解决。因此引出了新的方法:分页方式。分页的方式实际上就是讲内存以4KB为单位分页(一页4KB),然后在Linux内核中提供页项目表、页表,一个大小占多个页的进程,在运行的时候,并不是所有的也都在运行,这时候将运行的页拷贝到内存,这样就缓解了效率的问题。
5、进程虚拟4G内存空间
对于硬件来说只有4G的实际物理地址,每个程序在编译的时候,都在链接阶段,将elf程序虚拟地址设置在0x8048000开始,解决程序运行地址不固定的问题.
32位平台的进程地址空间
https://www.hackerearth.com/zh/practice/notes/memory-layout-of-c-program/
https://www.geeksforgeeks.org/memory-layout-of-c-program/
进程虚拟地址空间包含:代码段、数据段、BSS段、堆、栈、环境变量、内存映射区间(如共享库加载、mmap等)、内核空间等。
64位平台的进程地址空间
GCC:GUN Compiler Collection
gcc包揽整个编译过程
-E
选项:该选项只对文件进行预处理,预处理的输出结果被送到标准输出。-S
选项:使用该选项会生成一个后缀名为.s
的汇编语言文件,但是同样不会生成可执行程序。-c
选项:该选项告诉GCC编译器仅把源程序编译为目标代码而不做链接工作GNU Make
GNU Make简介
Makefile包含什么?
GNU Make工作原理
Makefile
Makefile
var := value # 在变量定义时展开
var = value # 在变量使用时展开
var ?= value # 在变量为空时赋值
var += value # 给变量追加值(在原值末尾追加)
$@ # 当前规则的目标
$< # 第一个依赖
$^ # 所有依赖
$? # 所有比目标更新的依赖
$* # 模式匹配规则中,匹配模式的部分
$(MAKE) # 指代make命令自身
$(shell cmdline)
$(wildcard *.c)
$(subst srcstr,deststr,text)
$(patsubst %.c,%.o,$(wildcard *.c))
$(SRC:%.c=%.o)
include foo.mak
make -C subdir
各种调试工具的使用
添加调试日志
代码审查
Linux系统常用调试工具
每个进程拥有独立的用户空间
每个进程拥有两个栈
用户栈:位于进程的用户空间
内核栈:位于系统的内核空间
进程的堆栈
内核在创建进程的时候,在创建task_struct的同时,会为进程创建相应的堆栈。每个进程会有两个栈,一个用户栈,存在于用户空间,一个内核栈,存在于内核空间。
当进程在用户空间运行时,cpu堆栈指针寄存器里面的内容是用户堆栈地址,使用用户栈;
当进程在内核空间运行时,cpu堆栈指针寄存器里面的内容是内核栈空间地址,使用内核栈。
进程用户栈和内核栈的切换
当进程因为中断或者系统调用而陷入内核态之行时,进程所使用的堆栈也要从用户栈转到内核栈。
进程陷入内核态后,先把用户态堆栈的地址保存在内核栈之中,然后设置堆栈指针寄存器的内容为内核栈的地址,这样就完成了用户栈向内核栈的转换;当进程从内核态恢复到用户态之行时,在内核态之行的最后将保存在内核栈里面的用户栈的地址恢复到堆栈指针寄存器即可。这样就实现了内核栈和用户栈的互转。
那么,我们知道从内核转到用户态时用户栈的地址是在陷入内核的时候保存在内核栈里面的,但是在陷入内核的时候,我们是如何知道内核栈的地址的呢?
关键在进程从用户态转到内核态的时候,进程的内核栈总是空的。这是因为,当进程在用户态运行时,使用的是用户栈,当进程陷入到内核态时,内核栈保存进程在内核态运行的相关信息,但是一旦进程返回到用户态后,内核栈中保存的信息无效,会全部恢复,因此每次进程从用户态陷入内核的时候得到的内核栈都是空的。所以在进程陷入内核的时候,直接把内核栈的栈顶地址给堆栈指针寄存器就可以了。
进程的地址空间由哪些部分组成?
进程启动:fork()
+ execve()
exec
函数时,该进程执行的程序完全替换为新程序,而新程序则从其main
函数开始执行。因为调用exec
并不创建新进程,所以前后的进程ID并未改变。exec
只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段。进程退出
exit() / main
函数执行return语句被动退出:信号(SIGKILL、SIGTERM、SIGABRT、SIGBUS、SIGSEGV等等)
main
返回exit
_exit
或_Exit
pthread_exit
abort
shell命令运行的进程是怎么启动的?
获取命令行
解析命令行
建立一个子进程(fork)
替换子进程(execvp)
父进程等待子进程退出(wait)
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <fcntl.h>#include <unistd.h>#include <ctype.h>#include <sys/wait.h>void do_exe(char* buf, char* argv[]) //加载程序{pid_t pid = fork();if(pid == 0)//子进程{execvp(buf, argv);perror("fork"); //执行到此,说明execvp未执行成功,fork失败exit(1);}wait(NULL); //等待子进程死亡, 回收}//对命令进行解析void do_parse(char* buf){char* argv[8] = {}; //将buf中的命令以‘ ’为分界存入指针数组中int argc = 0;int status = 0; //一个新的字符串for(int i =0; buf[i] != 0; ++i){if(status ==0 && !isspace(buf[i])){argv[argc++] = buf +i;status = 1;} else if(isspace(buf[i])){status = 0;buf[i] = 0;}}argv[argc] = NULL;do_exe(buf, argv);}int main(void){// char* argv[] = {"ls", "-lah", NULL};// execvp("ls", argv);//替换地址空间,实则将原进程的代码段,数据段进行替换,并未创建新的进程出来。char buf[1024] = {};while(1){printf("my shell#");memset(buf, 0x00, sizeof(buf));//[^\n]匹配除\n以外的所有字符,*用于抑制转换//scanf成功返回输入的项数while(scanf("%[^\n]%*c", buf) == 0) { //为0表示只输入了换行printf("my shell#");while(getchar() != '\n'); //到获得了一个‘\n'}do_parse(buf);}return 0;}
+ 在Ubuntu终端bash上输入`ps -o pid,ppid,pgid,sid,tpgid,comm`可以得到:```bashmarvin@marvin-VirtualBox:~$ ps -o pid,ppid,pgid,sid,tpgid,commPID PPID PGID SID TPGID COMMAND11554 11552 11554 11554 16339 bash16339 11554 16339 11554 16339 ps```可以发现,`ps`的父进程是`bash`,同时他们位于同一个会话组中。更详细的解释见《UNIX环境高级编程》9.9小节
fork()
创建子进程fork()
的过程 《Linux 内核设计与实现》
+
创建子进程的PCB并将子进程加入系统调度队列
复制父进程的用户空间数据和系统对象等(父进程各种数据都拷贝给子进程)
复制父进程的当前线程
一个系统调用,两次返回
pid_t getpid()
:获取当前进程IDpid_t getppid()
:获取父进程IDpid_t getpgid(pid_t pid)
:获取指定进程所属的进程组ID getpgid(0)
:获取当前进程的进程组ID,相当于getpgrp()int setpgid(pid_t pid, pid_t pgid)
:创建新的进程组ID 或 移动进程到新的进程组(同一个会话内)setpgid(0, 0)
:当前进程的进程组ID设置为当前进程ID,相当于setpgrp()pid_t getsid(pid_t pid)
:获取指定进程的会话ID pid_t setsid()
:创建一个新会话,当前进程为新的进程组长,且没有控制终端
进程状态
Linux进程的5种状态:运行、可中断的等待、不可中断的等待、停止、僵死
运行:R(Running)
等待:Waiting
停止:T(Traced or Stopped)
僵死:Z(Zombie)什么情况下会产生僵尸进程
wait
时使用,此时子进程变成了不在活动的进程,即僵死进程。僵死进程虽然不再活动,但是仍然占有一定的系统资源,直到父进程调用wait
获得子进程的终止信息后,才最终予以释放。 //filename: zombie.c#include<stdio.h>#include <stdlib.h>#include <sys/types.h>#include <unistd.h>int main(){pid_t child_pid;/*创建子进程*/child_pid = fork();if(child_pid >0){sleep(60);/*父进程休眠60秒*/} else {/*子进程退出*/exit(0);}return 0;}
$ ps -a -o pid,ppid,s=state,command
得到结果
PID PPID state COMMAND23553 22999 S ./zombie23554 23553 Z [zombie] <defunct>23609 23455 R ps -a -o pid,ppid,s=state,command
可以看到此时子进程处于僵死状态,state
显示为Z
。这是因为父进程休眠60秒,在休眠期间子进程得到执行并立即执行exit
,先于父进程退出,此时父进程还没有对子进程进行善后,因此已经退出的子进程仍然占用少量的系统资源。
为了避免僵死进程长期存在,父进程希望知道子进程何时结束运行。可以通过让父进程调用wait或waitpid暂时停止执行,等待子进程终止运行后,再继续执行父进程。
特殊进程
fork()
两次;wait()
或者waitpid()
,通知内核释放僵尸进程;守护进程:精灵进程(Daemon Process)
应用程序到进程的蜕变
fork()
+ 子进程execve()
execve()
exec()
家族 execl(const char *path, const char *arg...)
path
:程序绝对路径arg...
:可变参数列表,最后一个参数必须是(char *)NULLexeclp(const char *file, const char *arg...)
file
:可以是相对路径,在$PATH中寻找execle(const char *path, const char *arg..., char * const envp[])
envp[]
:字符串数组形式的环境变量列表,元素形式“name=value”,最后一个元素总是NULLexecv(const char *path, char * const argv[])
argv[]
:字符串数组形式的参数列表,最后一个元素总是NULLexecvp(const char *file, char * const argv[])
execvpe(const char *file, char * const argv[], char * const envp[])
execve(const char *path, char * const argv[], char * const envp[])
exec()
家族的函数,最后都是调用这个系统调用函数shell命令和脚本的运行
int system(const char *cmdline)
FILE *popen(const char *cmdline, const char *mode)
int pclose(FILE *stream)
进程资源的回收
exit()
int atexit(void (*func)(void))
int on_exit(void (*func)(int, void *), void *arg)
pid_t wait(int *status)
pid_t waitpid(pid_t pid, int *status, int options)
signal(SIGCHLD, SIG_IGN)
进程异常退出
ulimit -c:0(不产生)/ size(core文件最大block数)/ unlimited(无限制,推荐值)
cat /proc/sys/kernel/core_pattern
SIGQUIT / SIGILL / SIGTRAP / SIGABRT / SIGBUS / SIGSEGV / SIGFPE
信号量:Semaphore
count > 0 => count--; count == 0 => waiting
someone waiting => wake up; nobody waiting => count++
二元信号量
System V Semaphore
创建/获取:man semget
控制/初始化:man semctl
操作(P/V):man semop
消息队列:Message Queue
System V Message Queue
创建/打开:man msgget
控制/初始化:man msgctl
操作:发送/接收
man msgsnd
+ 自定义消息格式:type + data[0..N]
+ 接收:man msgrcv
共享内存:Shared Memory
System V Shared Memory
man shmget
man shmctl
操作:连接/分离
man shmat
shmaddr:推荐值 => NULL
分离:man shmdt
System V IPC用法总结
三个步骤:
IPC对象的键值:
IPC_PRIVATE
:只能用于具有亲缘关系的进程之间的通信ftok
:生成唯一的键值,可用于任意进程之间的通信select/poll
使用pipe()
:创建一对文件描述符 fork()
:创建两个进程 fork()
机制,只能用于具有亲缘关系的进程之间的通信UNIX FIFO
mkfifo()
:创建管道文件 open()
:打开管道文件,用于读写 open()
为止,反之亦然read()
:从FIFO中读取数据 write()
:往FIFO中写入数据 信号(Unix Signal)
软件层次对中断机制的一种模拟
进程收到信号 VS. 处理器收到中断请求
信号与中断的共同点
(1)采用了相同的异步通信方式;
(2)当检测出有信号或中断请求时,都暂停正在执行的程序而转去执行相应的处理程序;
(3)都在处理完毕后返回到原来的断点;
(4)对信号或中断都可进行屏蔽。
异步请求
随时打断
注册处理函数
信号与中断的区别:
(1)中断有优先级,而信号没有优先级,所有的信号都是平等的;
(2)信号处理程序是在用户态下运行的,而中断处理程序是在核心态下运行;
(3)中断响应是及时的,而信号响应通常都有较大的时间延迟。
什么是中断
信号来源:
软件来源:调用函数触发、软件条件触发、发生非法运算等
信号的处理方式
常见信号含义
+ `kill -l`:列出系统支持的所有信号
+ 详细的信号含义:参考《UNIX环境高级编程》第10章 信号
+ <font color=red>两个信号不可捕捉或忽略:`SIGKILL`, `SIGSTOP`</font>
信号的常见用途
终止进程
进程间事件通知
kill()/raise()
sigqueue()
alarm()/setitimer()
abort()
pause()/sleep()
注册信号处理函数
signal:man signal
+ `sigaction:man sigaction`
信号处理函数编写的注意事项:在信号处理函数中不得调用不可重入函数(参见《UNIX环境高级编程》表10-3)
信号处理可重入
如何编写可重入函数?
1)不在函数内部使用静态或全局数据结构
2)不返回静态或全局数据结构,所有数据都由调用者提供
3)使用本地数据(局部变量),或通过制作全局数据的本地拷贝来使用全局数据
4)不调用malloc/free函数
5)不调用不可重入函数
UNIX Domain Socket(AF_UNIX)
TCP/UDP Socket(AF_INET)
socket使用过程
面向连接的socket通信过程举例(类比电话通信)
pthread:用户线程
用户进程的最小执行实体
进程中的所有线程,共享进程资源
线程之间有哪些资源不共享?
拥有独立的运行栈
线程的优点:
线程独有的资源:
并发与并行
主线程:进程入口运行的第一个线程,默认线程(入口:main函数)
pthread_t:线程ID(线程唯一标识)
创建线程:pthread_create
+ **线程创建后就开始运行吗?**
终止线程:
从线程入口函数返回
被其他线程终止:pthread_cancel()
线程主动退出:pthread_exit()
如果从主线程main函数返回呢?
exit(main(...))
。main执行完之后, 会调用exit()
。exit()
会让整个进程终止,那所有线程自然都会退出。https://blog.csdn.net/fivedoumi/article/details/51863931如果线程内调用exit()退出呢?
如果进程中的任一线程调用了exit,Exit或者_exit,那么整个进程就会终止。https://blog.csdn.net/wyq393562305/article/details/24420679
单个线程可以通过下列三种方式退出,在不终止整个进程的情况下停止它的控制流。
(1)线程只是从启动例程中返回,返回值是线程的退出码。
(2)线程可以被同一进程中的其他线程取消。
(3)线程调用pthread_exit。
线程属性:pthread_attr_t
如果man pages中找不到pthread相关的接口,可能是未安装,可以尝试:sudo apt install manpages-posix
线程的连接与分离:决定一个线程以什么方式结束自己
pthread_detach
pthread_detach(pthread_self())
pthread_attr_setdetachstate
更多线程相关函数
线程安全(Thread Safety)
程序同时执行多个线程却不会“破坏”共享数据或者产生“竞争”条件的能力
线程安全函数:被多个并发线程反复调用仍然可以保证结果正确性
如何确保线程安全?
线程安全函数 VS. 可重入函数
可重入函数一定是线程安全的
线程安全函数不一定是可重入的
为什么是线程安全的?因为glob是静态变量,是所有线程共享的,于是每次incer函数执行时都能读取正确的为什么不是可重入的?因为函数incer调用过程中如果其他函数修改了静态变量glob的值,那么函数中断后无法保证glob的值的正确性
线程同步
线程同步手段主要包括:互斥锁、读写锁、条件变量。
sudo apt install manpages-posix-dev
互斥量
线程互斥锁
保护线程的临界区(共享资源)
同一时刻只能有一个线程持有
对比信号量
使用接口
pthread_mutex_init
pthread_mutex_destroy
pthread_mutex_lock
pthread_mutex_unlock
pthread_mutex_trylock
+ 思考:如果有多个共享变量,该如何设计线程锁?+ 每个共享变量独立一把锁?+ 每个共享变量独立一把锁:锁太多,容易导致死锁,而且开销也大+ 所有共享变量共用一把锁?+ 所有共享变量共用一把锁:需要保护的临界区太大+ 线程锁的设计,要讲究平衡,兼顾性能与程序复杂度,避免死锁
读写锁
pthread_rwlock_init / pthread_rwlock_destroy
pthread_rwlock_rdlock / pthread_rwlock_tryrdlock
pthread_rwlock_wrlock / pthread_rwlock_trywrlock
pthread_rwlock_unlock
条件变量
资源什么时候可用?轮询是一种方式,但是代价太高,浪费资源。条件变量是一种很好的替代方法
事件等待与通知
通常结合互斥锁一起使用
互斥锁阻止共享数据的并发访问,条件变量通告共享数据的状态变化
Q:一定要使用条件变量吗?
支持定时等待,事件发生或超时后都会返回
支持一次唤醒一个等待线程,也支持批量唤醒所有等待线程(广播)
使用接口:
pthread_cond_init
pthread_cond_destroy
pthread_cond_wait
pthread_cond_timedwait
通知(唤醒单个等待线程):pthread_cond_signal
广播(唤醒所有等待线程):pthread_cond_broadcast
典型的生产者和消费者问题的解法示例
线程死锁
产生死锁的必要条件
进程之间也会发生死锁
如果只有一个线程,会不会发生死锁?
如何避免死锁?
Linux系统对死锁的处理:鸵鸟算法
设计阶段如何避免死锁?
避免交叉等待:让所有线程使用相同的顺序获取锁
foo_lock(){lock(lock1);lock(lock2);lock(lock3);}foo_unlock(){unlock(lock1);unlock(lock2);unlock(lock3);}
定时器:定时产生特定事件或执行特定任务
创建定时器
timer_create
clockid
:定时器基于哪种时钟创建 sevp
:设置定时器到期时的通知方式和处理方式 memset
)SIGEV_NONE
)SIGEV_SIGNAL
)SIGEV_THREAD
)timerid
:保存创建后的定时器句柄
启动定时器
timer_settime(timerid, flags, new_time, old_time)
启动/停止/重置定时器
flags:
new_time:struct itimerspec
+ old_time:+ 可以是NULL+ 否则用来保存旧的到期时间
删除定时器
timer_delete(timerid)
查看定时器到期时间
timer_gettime(timerid, curr_time)
常用socket类型:
socket地址类型:
socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIX Domain Socket。虽然网络socket也可用于同一台主机的进程间通讯(通过loopback地址127.0.0.1),但是UNIX Domain Socket用于IPC更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。这是因为,IPC机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的。UNIX Domain Socket也提供面向流和面向数据包两种API接口,类似于TCP和UDP,但是面向消息的UNIX Domain Socket也是可靠的,消息既不会丢失也不会顺序错乱。
UNIX Domain Socket是全双工的,API接口语义丰富,相比其它IPC机制有明显的优越性,目前已成为使用最广泛的IPC机制,比如X Window服务器和GUI程序之间就是通过UNIX Domain Socket通讯的。
使用UNIX Domain Socket的过程和网络socket十分相似,也要先调用socket()创建一个socket文件描述符,address family指定为AF_UNIX,type可以选择SOCK_DGRAM或SOCK_STREAM,protocol参数仍然指定为0即可。
UNIX Domain Socket与网络socket编程最明显的不同在于地址格式不同,用结构体sockaddr_un表示,网络编程的socket地址是IP地址加端口号,而UNIX Domain Socket的地址是一个socket类型的文件在文件系统中的路径,这个socket文件由bind()调用创建,如果调用bind()时该文件已存在,则bind()错误返回。
网络字节序
网络通信使用的字节序(大端)
字节序是数据在计算机硬件中的存储方式。
大端字节序:高位字节在前,低位字节在后,即内存低地址保存高位字节,高地址保存低位字节。例如0x12345678,保存顺序为“12 34 56 78”,与人类对数据的阅读方式相同。
小端字节序:低位字节在前,高位字节在后,即内存低地址保存低位字节,高地址保存高位字节。例如0x12345678,保存顺序为“78 56 34 12”,与人类对数据的阅读方式相反。
与主机字节序(大端/小端)之间的转换:
socket编程模型
无连接的socket通信过程
如果不需要传递flag,也可以使用read/write接口
多路I/O复用
为什么需要多路选择IO?
有哪些API可用于多路选择IO?
select
int select(nfds, readfds, writefds, exceptfds, timeout)
fd_set
:与监视文件建立一一映射
最常用的fd_set:readfds
void foo(void){...;int server_fd, client_fd = -1;/* Server socket creating & binding here, and do listening */...;while (1) {struct timeval timeout = ...;fd_set rfds;int ret, nready;int max_fd;FD_ZERO(&rfds);FD_SET(server_fd, &rfds);if (client_fd >= 0)FD_SET(client_fd, &rfds);max_fd = (server_fd >= client_fd ? server_fd : client_fd);nready = select(client_fd + 1, &rfds, NULL, NULL, &timeout);if (nready < 0) {src_printf(SRC_ERR, "select error: %s\n", strerror(errno));/* You can add your error handler here */sleep(1);continue;} else if (nready == 0) {/* Do your timeout handler here */...;continue;}if (FD_ISSET(server_fd, &rfds)) {src_printf(SRC_DEBUG, "Accepting...");/* client connecting server, do accept() */...;if (--nready <= 0)continue;}if (FD_ISSET(client_fd, &rfds)) {/*** client could be read.* You can read() or recv() msg here.*/...;}}...}
#include <sys/types.h>#include <sys/times.h>#include <sys/select.h>int select(nfds, readfds, writefds, exceptfds, timeout)int nfds;fd_set *readfds, *writefds, *exceptfds;struct timeval *timeout;ndfs:select监视的文件句柄数,视进程中打开的文件数而定,一般设为你要监视各文件中的最大文件句柄加一。 readfds:select监视的可读文件句柄集合。writefds: select监视的可写文件句柄集合。exceptfds:select监视的异常文件句柄集合。timeout:本次select()的超时结束时间。(见/usr/sys/select.h,可精确至百万分之一秒!) 当readfds或writefds中映射的文件可读或可写或超时,本次select()就结束返回。程序员利用一组系统提供的宏在select()结束时便可判断哪一个文件可读或可写。对Socket编程特别有用的就是readfds。 几个相关的宏解释如下:FD_ZERO(fd_set *fdset):清空fdset与所有文件句柄的联系。FD_SET(int fd, fd_set *fdset):建立文件句柄fd与fdset的联系。FD_CLR(int fd, fd_set *fdset):清除文件句柄fd与fdset的联系。FD_ISSET(int fd, fdset *fdset):检查fdset联系的文件句柄fd是否可读写,>0表示可读写。(关于fd_set及相关宏的定义见/usr/include/sys/types.h)
poll
epoll
int epoll_create(int size)
int epoll_create1(int flags)
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
+
socket编程常用API
socket
bind
listen
connect
accept
send / sendto / sendmsg
recv / recvfrom / recvmsg
close / shutdown
inet_addr
inet_aton
inet_ntoa
htons / htonl
ntohs / ntohl
getsockname
getpeername
getsockopt
setsockopt
select
poll
epoll
Linux下的一种init程序
System V init VS. system
System V init:串行启动服务
/etc/init.d/*
rcS, rc0~rc6
systemd:并行启动服务
foo.service
文件结构
[Unit]
[Service]
start/stop/restart
[Install]
enable/disable
[Unit]
Description=foo service description
After=network.target
[Service]
Type=forking
PIDFile=/var/run/foo.pid
ExecStart=/usr/local/bin/foo
Restart=always
[Install]
WantedBy=multi-user.target
服务foo.service
文件存放路径
/lib/systemd/system/foo.service
常用服务控制命令
systemctl start foo
systemctl stop foo
systemctl restart foo
systemctl enable foo
systemctl disable foo
Systemd 入门教程
版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。
工作时间:8:00-18:00
客服电话
电子邮件
admin@qq.com
扫码二维码
获取最新动态