黑马linux系统编程网课笔记

黑马linux系统编程网课

P1 linux命令

各种简单命令,tar find grep等

P17 vim命令与编译

  • 跳转

    • 跳转到66行

      • :66

      • 66G

  • gg行首

  • G行尾

  • gg=G格式化

  • %跳到最后一个大括号

  • 撤销

    • 撤销 u

    • 反撤销contrl r

  • dd删除一行

查看{d 宏定义

3K 查看当前函数的man


  • 文本代码文件 .c
    • gcc -E 预处理。处理宏定义,展开头文件。
  • 展开头文件的文本代码文件 .i
    • gcc -S 检查语法,编程汇编。得到的文件是.s
  • 汇编指令 .s
    • gcc -c变成机器代码.o
  • 机器语言 .o
    • 链接,数据段合并,地址回填
  • 机器语言 .out

gcc -o是起名字的 gcc -I 指定头文件所在目录 -Wall所有警告都显示

​ 编译消耗时间gcc编译过程

1
//gcc -S是检查, 要输出文件要加-o, 会生成文件
  • gcc -S是检查, 要输出文件要加-o, 会生成文件

  • gcc -c 可以添加多个文件

  • 静态库

    • 静态库一般是.a文件编译

      • 1
        2
        3
        4
        5
        6
        7
        8
        
        gcc -c first.c sencond.c  				 #得到.o文件
        ar -r lib库的名称.a first.o sencond.o		#得到库文件
        #	用rcs也行
        gcc main.c -l库的名称 								#使用
        gcc main.c libname.a 	#库的.a文件要放在最后面
        gcc main.c -lpcap -L../pcap10.2.1
        -L是.a的所在路径的文件夹名
        -l后加库名称, 一般库叫libxx.a, 名称就是xx
        
    • 使用就是 gcc main.c 库的名称

  • 动态库

    • 制作后得到.so后缀

      • 1
        2
        3
        4
        5
        6
        7
        
        gcc -c -fpic add.c minus.c	 				#得到.o文件
        gcc -shared add.o minus.o lib库名称.so 	#得到库文件
        gcc -shared -o lib库名称.so  add.o minus.o	#得到库
        gcc main.c -l库的名称 -L/库所在的文件夹路径 			 #使用
        
        gcc -o myprogram myprogram.c -L/path/to/libs -lexample #使用自带库
        gcc -o myprogram myprogram.c -L/path/to/libs -lexample -Wl,-rpath=/path/to/libs	#使用自己的库
        
    • 动态库的使用,

      1. 要环境变量让动态链接库找到库,export= LD_LIBRARY_PATH=库的路径
      2. 把自己的库放在/lib里面也可以, 就会去/lib里面找了
      3. vim ~/.bashrc 是脚本文件, 得重新启动才能好使
      4. 把库的路径放到/etc/ld.so.conf里面
        • 并生效, ldconfig -v
          • v的意思是展示给用户看的, tar -zcvf也是一样
    • ldd a.out

      • 查看运行时加载的动态库在哪
  • 查看自己有什么库

    • 1
      
      gcc -print-search-dirs | grep libraries
      
      • 1

P37 markfile

  1. 一个规则
  2. 两个函数
  3. 三个自动变量

1
2
3
4
5
6
7
targets : prerequisties
[tab]command
debug :
	@echo hello			#隐藏命令, 不隐藏输出

#伪目标 如果这个目标和一个文件重名了, 那么就会说这个文件不能生成了, 要用下面这个声明他不是目标文件, 是一个伪文件
.PHONY : clean
  • 变量的定义

    •  1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      
      cpp := main.cpp #常量, 后续不能更改
      obj := main.o
      
      cpp = main.cpp #变量, 可以更改
      obj = main.o
      
      cpp ?= main.cpp #如果之前有定义, 则不动, 如果没有赋值, 会赋值
      obj ?= main.o
      
      ${obj} : ${cpp} 			#用变量用${}
      	@g++ -c ${cpp} -o ${obj}
      
      目标文件的完整名称 可以用$@
      第一个依赖 可以用$<
      所有依赖文件 可以用$^
      
      • 12
  • 函数的使用

    • 1
      2
      
      cpp := $(subst <from>, <to>, <list>)  #字符串替换
      cpp := $(patsubst <from>/%.c, <to>/%.o, <list>)	#字符串替换
      
      • 处理字符串
    • 1
      2
      3
      4
      5
      
      #-I头文件路径
      include_paths := /usr/include \
      								/usr/include/opencv2/core
      include_paths := $(foreach item, $(include_paths), -I$(item))	#循环
      I_flag := $(include_paths:%=-I%)						#简单方法
      
      • 1
    • 1
      2
      3
      
      objs/%.o : src/%.cpp
      	@mkdir -p $(dir $@) # $@是目标文件完整名字
      	@gcc -c $^ -o $@
      
      • 编译出目标文件
    • 1
      
      filter-out(<delet>,<from>)
      
      • 可以用通配符来过滤不想要的文件

P50

write()函数写到文件里,会有一个缓冲区,操作系统自带的

  • 操作系统书里写过,要有一个缓冲区,写满后一起放到物理存储设备中。

操作系统利用PID(Process Identifier)找到PCB(Process Control Block)。

  • 每个进程都有自己的唯一的PID,通过PID找到PCB
  • PCB的本质是结构体,这个结构体里面有一个指针,指向文件描述符表,这个表是键值对的,可能是红黑树做的。
  • 表中 0 表示标准输入(stdin),1 表示标准输出(stdout),2 表示标准错误输出(stderr),而其他非负整数值表示其他打开的文件。
  • 因为默认这个表是1024,所以一个进程只能打开1024-3个文件。linux_PCB表

dentry 和 inode 。

  • dentry就是dirent结构体,里面有文件名和指针等,指针指向inode结构体。
  • inode里面有文件权限,大小,位置的指针,指针指向文件的磁盘地址。
  • opendir()返回一个dir指针
  • readir()返回一个dirent的结构体指针。输入参数是dir指针。innode

如果把目录文件的权限改成不同的rwx

  • a-r就不能读了

  • a-w就不能在目录里添加文件了

  • a-x就不能cd进入目录了

lseek()函数偏移量,返回从头到光标的偏移量

  • off_t lseek(int fd, off_t offset, int whence);
  • whence起始位置
    • SEEK_SET 从头
    • SEEK_CUR 现在为止
    • SEEK_END 结尾

查看文件属性

  • int stat(const char *restrict path, struct stat *restrict buf);
    • 穿透查看文件各种属性的
  • int lstat(const char *restrict path, struct stat *restrict buf);
    • 不穿透查看文件各种属性

用buf.st_size()获取文件大小

用buf.st_mode()获取文件类型

Man 2 stat 里面有写,用宏获取文件类型,参数m就是buf.st_mode()。

1
2
3
4
5
6
7
S_ISREG(m)  is it a regular file?
S_ISDIR(m)  directory?
S_ISCHR(m)  character device?
S_ISBLK(m)  block device?
S_ISFIFO(m) FIFO (named pipe)?
S_ISLNK(m)  symbolic link?
S_ISSOCK(m) socket?
  • 这章作业,总结以上所学的,递归实现遍历文件夹。
    • 已经实现

P68

重定向

  • int dup(int oldfd)
    • 创建一个文件描述符(根据红黑树,就是选最小的数的文件描述),来指向参数里fildes所指向的文件
  • int dup2(int oldfd, int newfd);
    • 先判断旧的文件描述符valid(有效的),
      • 如果不valid,返回失败
      • 如果valid,返回新的文件描述符的数字
    • 成功的话,新描述符就指向旧文件描述符的指向
    • newfd的数值可以自己指定

PCB进程控制块

  • 进程id(PID),进程的唯一标志符
  • 进程的状态
    • 初始
    • 就绪
    • 运行
    • 挂起(等待资源)
    • 结束
  • 进程切换时的寄存器里的内容
  • MMU,描述虚拟内容地址空间的信息
  • 描述控制终端的信息
  • 当前工作目录
  • 文件描述符
  • 和信号相关信息
  • 用户id和组id
  • 环境变量和main函数的命令行参数

环境变量

  • echo $PATH 显示环境变量路径
  • echo $SHELL 显示当前shell
  • echo $TERM 显示当前终端
  • echo $LANG 显示语言,字符集
  • echo $HOME 显示家目录
  • env 显示所有环境变量

进程

  • pid_t getpid(void) 获取自己的pid
  • pid_t getppid(void) 获取父进程pid

父子进程

  • 读时共享,写时复制
  • 不同的是
    • PID
    • 返回值
    • 进程创建时间
    • 闹钟
    • 未决信号集
  • 共享的是
    • 文件描述符
    • mmap映射区
  • fork()之后,父进程还是子进程哪个先执行不知道

P87

gdb调试

  • fork()之后会出现子进程,设置跟踪哪个进程
    • set follow-fork-mode parent
    • set follow-fork-mode child

exec函数族

  • 用了这个命令后,整个进程都会变成执行的进程,全部都改变了,变成了执行文件的模样
  • int execl(const char *path, const char *arg, …)
    • path 指定路径
    • arg 指定命令,后面的是参数
  • int execlp(const char *file, const char *arg, …)
    • file系统调用,只要名字就行了
    • arg 指定,后面是参数
  • int execv(const char *path, char *const argv[])
    • 用一个数组接收参数(vector)
  • int execvp(const char *file, char *const argv[])
    • 用一个数组接收参数(vector)的同时
    • 是使用环境变量查找的
  • int execle(const char *path, const char *arg, …, char * const envp[])
  • int execvpe(const char *file, char *const argv[],char *const envp[])
    • 这个函数族的参数代表
    • l(list) 命令行参数列表
    • p(path) 搜索file时用的path变量
    • v(vector) 使用命令行参数数组
    • e(environment) 使用环境变量数组
  • 用这个函数的时候记得参数后面加NULL, 要不然函数不知道你在哪结束, 这不是c++.
  • excve()是系统调用,上面都是用这个实现的

孤儿进程

  • 父亲死了,儿子就会进孤儿院,init进程下
    • 可以用kill杀掉
  • 儿子死了,父亲还在,儿子就变成僵尸进程了
    • 儿子就剩一个pcb表,里面有死亡原因.
    • 不可以用kill杀掉

wait()函数

  • pid_t wait(int *status) 会阻塞等待子进程死亡
    • status描述子进程状态
      • WIFEXITED(status) 子进程正常终止返回真
      • WEXITSTATUS(status) 返回子进程的返回值
      • WIFSIGNALED(status) 返回子进程被信号终止
        • WTERMSIG(status) 输出是什么信号终止的
    • 成功返回孩子的pid
    • 失败返回-1
    • wait(NULL) 不关心子进程的状态
  • pid_t waitpid(pid_t pid, int *status, int options)
    • 一次调用只能回收一个
    • pid
      • -1时 任意一个都可以回收
      • >0 指定一个pid 就回收这个pid子进程
      • 0 回收当前一个组的所有子进程
      • <0 指定一个-的pid,那就代表那个组
    • options
      • WNOHANG 不阻塞
    • \返回值>0时 回收子进程pid
    • ==0时 并且 options是WNOHANG时,没成功
    • \返回值<0时 失败
  • 所有异常情况终止,都是由于信号.
  • kill -l 可以看到所有信号 -9就是信号终止

管道

  • 伪文件, 只在内存中有数据,不在存储里
    • 管道
    • socket
  • 大小是4k,环形队列实现
  • 读取后,管道里就没数据了
  • 半双工
  • int pipe(int fildes[2])
    • fildes[0]读取
    • fildes[1]写入
  • 读管道
    • 有数据就读
    • 没数据
      • 写入端还能不能写了
        • 不能的话 返回0
        • 能的话 阻塞等待
  • 写管道
    • 读端被关闭了,那就异常终止
    • 没有全被关闭
      • 管道满了,阻塞等待
      • 没满,那就写入,返回成功写入的字节
  • 进程间通信方式
    • 信号
    • 管道
    • mmap映射
    • socket

有一道习题写出ps | wc -l

  • 这道题挺有意思, 找了一会bug,找到了,就是读管道的时候一直阻塞等待,因为没有一个control d返回信号,导致出不了结果, 怎么能让他出来那个结束信号,就是让管道不能再写入了,把能写入的信号全关了, wc命令就停止阻塞读取了.

P106进程间通信

  • FIFO
    • int mkfifo(const char *pathname, mode_t mode)
      • 本质就是创建一个文件, 性质和管道一样
      • mkfifo()
      • open()
      • writ()/read()
      • close()
  • 文件共享
    • 通过共同读或写一个文件实现共享数据
  • 存储映射(MMAP)
    • void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off)
      • addr 通常NULL
      • length 内存映射区大小
      • prot 内存映射区的读写属性
      • flags 标志共享内存属性 共享还是私有(意识是是否能从内存保存回储存)
      • fd 用于创建内存映射区的那个文件的 文件描述符
      • off 偏移位置.要是4k的整数倍 默认是0
      • 返回值
        • 成功 映射区的受地址
        • 失败 MAP_FAILED , -1
    • 创建后,可以把文件描述符可以关了, 用指针读写
    • ftruncate(fd, 4) 拓展文件大小

P124信号

信号量

  • 产生

    1. 按键产生

    2. 系统调用产生

    3. 软件条件产生

    4. 硬件异常产生

    5. 命令产生

  • 概念

    • 未决 产生与递达之间的状态, 就是pcb控制块已经是1了, 但还没有处理
      1. 处理有三种方式, 系统默认, 屏蔽字屏蔽, 捕捉(自定义)
    • 递达 达到系统了,并在处理

变量三要素

  • 名称
  • 类型
  • 数值

信号四要素

  • 编号
  • 名称
  • 事件
  • 默认处理动作

linux常规信号

  1. sighup 退出终端发的信号
  2. sigh control+c
  3. sigquit control+/
  4. sigill
  5. sigtrap
  6. sigabrt
  7. sigbus 总线错误
  8. sigfpe 除0操作
  9. sigkill 就是终止信号
  10. siguse1
  11. 段错误
    1. control + z 挂起
1
2
3
4
5
6
7
int kill(pid_t pid, int sig) //这个是用来发送信号的
  pid > 0 发送单个进程
  pid = 0 发送自己的一整个组
  pid = -1 发送自己有能力杀死的所有进程

  pid < -1 发送一整个组,id就是取绝对值的
  进程组的id是父进程的id

实际时间 = 用户时间 + 内核时间 + 等待时间(I/O)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
//alarm(2)返回值是剩余时间, alarm(0)是不用定时器了
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value)
	//ITIMER_REAL 	自然定时法
  //ITIMER_VIRTUAL	用户空间计时
  //ITIMER_PROF		内核+用户空间计时
//周期性的定时的时间
struct itimerval {
	struct timeval it_interval; /* Interval for periodic timer */
	struct timeval it_value;    /* Time until next expiration */
};
//第一次定时时间
struct timeval {
	time_t      tv_sec;         /* seconds */
	suseconds_t tv_usec;        /* microseconds */
};
1
2
sighandler_t signal(int signum, sighandler_t handler)
捕捉信号函数, 调用函数

关于屏蔽信号集 信号屏蔽字mask

1
2
3
4
5
6
7
8
9
  	int sigemptyset(sigset_t *set);//清空

  	int sigfillset(sigset_t *set); //全部1

   	int sigaddset(sigset_t *set, int signum);//添加

   	int sigdelset(sigset_t *set, int signum);//移除

   	int sigismember(const sigset_t *set, int signum);//判断在不在集合里
1
2
3
4
5
6
7
8
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
  how SIG_BLOCK 设置屏蔽
  		SIG_UNBLOCK
  		SIG_SETMASK 替换
  set 自定义set
  oldest 旧的set 传出参数
int sigpending(sigset_t *set); //查看未决信号集
	set传出参数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
  //signum 信号类型
/*struct sigaction {
               void     (*sa_handler)(int);
               sigset_t   sa_mask;  自己的屏蔽字, 并且会和系统里的mask取并集
               int        sa_flags; 默认0的话 会自己屏蔽自己的信号
           };*/
//oldact 传出参数
int sigemptyset(sigset_t *set);//清空
/*慢系统调用, 会永久阻塞的函数, read() wait()时. 如果在慢速系统调用时, 回来的时候重启的话, 不影响东西, sa_flags = SA_RESTART, 就是重启.
默认时不重启.

信号被捕捉期间会执行函数, 再有信号来的话, 会屏蔽, 并且多个信号来的话, 只会入队一个

P143会话与线程

进程组和会话

  • 多个进程组是会话
  • 进程组id是父进程id
  • 会话id是
  • ps ajx 查看进程
  • PPID(Parent Process ID)
  • PID(Process ID) 进程唯一key
  • PGID(Process Group ID) 进程组ID
  • SID(Session ID) 会话ID
  • UID(User ID)是用于标识用户的唯一标识符。
1
2
int chdir(const char *path) 改变工作目录
mode_t umask(mode_t mask);
  1. 关闭父进程,创建子进程

  2. 在子进程setsid()

  3. 改变当前工作目录

    • 防止目录被卸载
  4. 重设文件掩码

    • 022–755
    • 345–432
    • 4是r读
    • 2是w写
    • 1是x执行
    • 三个是因为文件所有者、文件所属组和其他用户
  5. 关闭文件描述符

  6. 开始执行守护进程

    1. 进程组长不能创建会话

      1
      
      pid_t setsid(void)//返回sid 失败返回-1
      
    2. 会话id, 三个id相等

      1
      2
      3
      
      getpid()
      getpgid(0)
      getsid(0)
      

线程

  • 进程

    • 有独立的地址空间
    • 有独立的PCB块
    • 最小分配资源单位
      • 可以看成一个线程的进程
  • 线程

    • 没有独立的地址空间
    • 有独立的PCB块
    • 最小执行单位
      • 一个线程可以看成一个进程, 去抢夺cpu运行
  • ps -Lf 进程id

    • 线程号
    • 线程池
    • mmu示意图 这个应该先是页目录, 然后页表, 然后页面, 最后是物理地址, 同时这个机构应该叫二级页表
  • 线程共享资源

    • 文件描述符
    • 没中信号处理方式
    • 当前工作目录
    • 用户id和组id
    • 内存地址空间
  • 线程不共享资源

    • 线程id
    • 处理线程(内核栈)
    • errno变量
    • 信号屏蔽字
    • 调度优先级
  • 线程优点

    • 提高程序并发
    • 开销小
    • 共享数据方便
      • 堆区共享
        • 动态分配的内存malloc new分配的
      • 栈不共享
        • 自己函数里定义的, 不共享
  • 线程缺点

    • 库函数, 不稳定
    • 不能GDB调试

  • 1
    2
    
    int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
    //-pthread编译用
    
    • thread线程id

    • attr属性

      • 1
        2
        
        int pthread_attr_init(pthread_attr_t *attr); //创建属性
        int pthread_attr_destroy(pthread_attr_t *attr);//destroy属性
        
        • 设置attr
        • 成功返回0
        • 用这个传出的attr, 当作下面函数的传入参数
      • 1
        2
        3
        
        int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
        int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
        //设置attr属性
        
      • PTHREAD_CREATE_DETACHED 分离

      • PTHREAD_CREATE_JOINABLE 非分离

    • start_routine回调函数

    • arg 指定回调函数的传入参数

      • 成功0
      • 失败errno
  • 1
    
    pthread_t pthread_self(void);
    
    • 返回本线程id
  • 1
    2
    3
    
    void pthread_exit(void *retval);
    /*return 退出函数
    exit() 退出主进程*/
    
    • 退出线程
  • 1
    
    int pthread_join(pthread_t thread, void **retval);//回收函数, 阻塞
    
    • thread 传入参数

    • retval 传出参数

    • 成功返回0

  • 1
    
    int pthread_cancel(pthread_t thread)//杀死线程
    
    • thread 传入参数
    • 需要有契机才能被杀死, 就是有系统调用函数
  • 1
    
    void pthread_testcancel(void);
    
    • 测试有没有pthread_cancel调用
  • 1
    
    int pthread_detach(pthread_t thread);//分离线程, 能自动回收资源
    
    • thread 分离哪个线程
  • 1
    
    fprintf(stderr,"%s",strerror(ret));//打印出错信息
    
    • 线程独有的出错检测

P165线程同步

锁是建议锁, 没有强制性, 应该在拿到数据前加锁

1
#include <pthread.h>

互斥锁

  • 1
    2
    3
    4
    5
    
    int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
    //动态初始化
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    //静态初始化
    int pthread_mutex_destroy(pthread_mutex_t *mutex);
    
    • init锁, destroy锁
    • restrict的意思是不能再复制出来一个指针
    • attr 即将创建的互斥量属性, 默认NULL
  • 1
    2
    3
    
    int pthread_mutex_lock(pthread_mutex_t *mutex);
    int pthread_mutex_trylock(pthread_mutex_t *mutex);//成功--, 失败返回.设置错误号EBUSY
    int pthread_mutex_unlock(pthread_mutex_t *mutex);
    
    • 加锁解锁, 加不了锁的话会阻塞, 尝试锁不阻塞
  • 1
    2
    3
    4
    5
    6
    
    /*
    使用粒度越小越好, 使用完之后快速解锁;互斥锁本质是结构体, 看成整数便于理解
    init()后把锁看成1
    加锁--
    解锁++
    */
    

读写锁

读共享, 写独占; 读写同时的话写锁权限高, 因为先读的话, 写会改变的, 没用

  • 1
    2
    3
    4
    5
    
    int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
    //动态初始化
    pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
    //静态初始化
    int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
    
    • 初始化
  • 1
    2
    3
    
    int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
    
    • 加锁
  • 1
    
    int pthread_rwlock_unlock(pthread_rwlock_t *rwlock)
    
    • 解锁

形成死锁

  1. 互斥条件(Mutual Exclusion)
    • 至少有一个资源被标记为独占资源,即一次只能由一个进程(线程)使用。其他进程(线程)必须等待该资源的释放。
  2. 请求与保持条件(Hold and Wait)
    • 一个进程(线程)在等待其他进程(线程)释放资源的同时,继续持有已经获得的资源。
  3. 不可剥夺条件(No Preemption)
    • 已经分配给一个进程(线程)的资源不能被强制性地剥夺,只能由持有资源的进程(线程)显式地释放。
  4. 循环等待条件(Circular Wait)
    • 存在一个进程(线程)的资源请求序列,每个进程(线程)都在等待下一个进程(线程)所持有的资源。

条件变量

  • 1
    2
    3
    4
    5
    
    int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
    //动态初始化
    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    //静态初始化
    int pthread_cond_destroy(pthread_cond_t *cond);
    
    • 初始化锁
  • 1
    
    int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);//等待
    
    • 解锁
    • 等待
    • 加锁
  • 1
    2
    
    int pthread_cond_signal(pthread_cond_t *cond);//唤醒至少一个
    int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒所有
    

Reactor模型

reactor模型

信号量(semaphore)

相当于初始化为N的互斥量, 信号和信号量没关系, 可以应用在线程、 进程中同步

  • 1
    2
    
    int sem_init(sem_t *sem, int pshared, unsigned int value);
    int sem_destroy(sem_t *sem);
    
    • pshared
      • 0–线程间同步
      • 1–进程间、线程同步
    • value(指定同时访问的线程数)
  • 1
    2
    3
    4
    5
    
    int sem_wait(sem_t *sem); //--操作
    int sem_trywait(sem_t *sem);//尝试--(不阻塞)
    int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);//绝对时间(absolutely), 1970.1.1 0:00
    //公历就是耶稣诞生, 佛历从释迦摩尼诞生算
    int sem_post(sem_t *sem);//++操作
    
    • struct timespec *abs_timeout
      • 毫秒
Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计