title: linux/unix编程手册-26_30


title: linux/unix编程手册-26_30 date: 2018-06-20 11:53:07 categories: programming tags: tips

linux/unix编程手册-26(监控子进程)

wait()
#include<sys/wait.h>

pid_t wait(int *status);

复制代码
  • 阻塞直到一个子进程终止
  • status非空则子进程终止信息通过status指向的int变量返回
  • 内核为父进程下所有子进程的运行总量追加进程CPU时间和资源使用数据
  • 终止子进程ID作为返回
  • 如果wait()返回-1,可能的原因是无等待子进程,此时errno会被置位ECHILD

局限性

  • 无法等待某个特定子进程完成
  • 无法非阻塞等待
  • 只能检测终止,对于停止或其他状况无法检测
waitpid()
#include<sys/wait.h>

pid_t waitpid(pid_t pid, int *status, int options);

复制代码
  • status 和 返回值和wait()一致
  • pid>0,为等待进程的pid
  • pid=0,为父进程同一进程组的所有子进程
  • pid<-1,取绝对值
  • pid=-1,为任意子进程,wait(&status) eq waitpid(-1, &status, 0)
  • option = WUNTRACED:除了子进程终止信息外,还返回因信号而停止的进程信息
  • option = WCONTINUED:返回收到SIGCONT信号而恢复执行的已停止进程的状态信息
  • option = WNOHANG:指定子进程未发生改变立刻返回不会阻塞,如果无匹配pid的子进程,waitpid()报错,错误号设置为ECHILD
  • 克隆子进程会有option扩展,见28章
status所指代值的意义

<sys/wait.h>定义了一组宏对status处理

  • WIFEXITED(status):子进程正常退出返回true
  • WIFSIGNALED(status):通过信号杀掉子进程为true,WTERMSIG(status)返回信号编号,WCOREDUMP(status)true表明生成核心转储文件
  • WIFSTOPPED(status):因信号停止返回true, WSTOPSIG(status)返回信号编号
  • WIFCONTINUED(status):因受到SIGCONT恢复执行返回true
从信号处理器函数终止进程

如果在函数中调用_exit(EXIT_SUCCESS)父进程会认为子进程正常终止

// 函数应当如下,wait才能正常捕捉是信号导致终止
void handler(int sig){
    /*func body*/
    signal(sig, SIG_DFL);
    raise(sig);
}
复制代码
系统调用waittid()wait3()wait4()
  • waittid()更精准的控制,略
孤儿进程和僵尸进程

孤儿进程: 父进程终止后,init进程接管子进程, getppid()返回1 僵尸进程: 在父进程调用wait()之前,子进程就已经终止

  • 系统允许父进程在之后某一时刻执行wait(),确定子进程为何
  • 内核通过将子进程转化为僵尸进程处理此情况,内核将释放子进程大部分资源,仅保留内核进程表中的一条记录(包含子进程ID,终止状态,资源使用数据)
  • 无法通过信号杀死僵尸进程,即使SIGKILL也不行
  • 父进程调用wait(),或者父进程未调用后退出,init接管后自动调用wait(),从系统移除僵尸进程
SIGCHLD信号

在一个进程终止或者停止时,将SIGCHLD信号发送给其父进程

如果每次收到SIGCHLD的时调用wait()因为在调用时对同一信号(SIGCHLD)要么忽略要么阻塞,阻塞的话标准信号之会保留一次,可能会遗漏SIGCHLD信号。

一个比较丑的解决僵尸进程的方法(调用前有僵尸进程产生不能解决)

while(waitpid(-1, NULL, WNOHANG) > 0)
    continue;
复制代码

一些特殊

  • 通常子进程停止也会向父进程发送SIGCHLD
    • 通过sigaction()调用,传入SA_NOCLDSTOP,可取消这个行为
  • 虽然默认SIGCHLD是忽略
    • 如果显示设置SIGCHLD为SIG_IGN,之后不会把符合条件的子进程其转化为僵尸进程,而是直接删除(有些UNIX实现可能会把之前僵尸进程给删除)
    • 通过sigaction()调用,传入SA_NOCLDWAIT,也可终止僵尸进程的产生,但是不保证是否发送SIGCHLD信号

linux/unix编程手册-27(程序的执行)

#include<unistd.h>

int execve(const char *pathname, char *const argv[], char *const envp[])

// envp 成员字符串格式为name=value
复制代码
  • 如果pathname文件设置了set-user-id权限位,会将有效用户ID设为文件属主ID
  • 不论有没set-user-id,均会将当前进程的有效用户id覆盖已保存的set-user-id
解释器脚本

文件描述符和exec()

默认由exec()调用程序所打开的所有文件描述符在exec()执行过程中都会保持打开

$ ls /tmp > dit.txt
复制代码
execlp()
复制代码
  • 调用fork()创建子进程
  • 子shell进程以描述符1打开dir.txt用于输出,打开的方式可能如下:
    • 子shell关闭描述符1,随机打开文件dir.txt,因为open总是取最小值,标准输入0此时已经打开
    • 打开dir.txt 如果获取的描述符不是标准输出,shell会调用dup2强制将标准输出复制为新描述符的副本dup2(fd, STDOUT_FILENO), 之后再close(fd)
  • 子进程执行ls, 将结果输出到标准输出

shell有内建命令如cd,不会调用fork()或者exec()

执行时关闭标志(FD_CLOEXEC)

  • 库函数应该总是为其打开文件设置执行时关闭标志
  • 如果设置了执行时关闭
    • 成功执行exec()时, 会自动关闭此描述符
    • 如果调用exec()失败,文件描述符会保持打开
信号与exec()

执行shell命令。system()
#include<stdlib.h>

int system(const char *command);

system(cmd);

//等同于

execl("/bin/sh", "sh", "-c", cmd, (char *) NULL);
复制代码

linux/unix编程手册-28(详述进程的创建和程序的执行)

进程记账
#define _BSD_SOURCE
#include<unistd.h>

int acct(const char *acctfile)

// 特权进程可以调用, acctfile 通常是路径字符串/var/log/pacct 或 /usr/account/pacct的指针,若取消记账功能,accfile为NULL即可
复制代码
系统调用clone()
#define _GNU_SOURCE
#include<sched.h>

int clone(int (*func)(void *), void *child_stack, int flags, void *func_arg, ...)
复制代码
  • flags 由高位和低位组成
    • 低位保存子进程终止时发个父进程的信号, 为0则不发送,如果子进程是被信号终止,则还是发SIGCHLD给父进程
    • 高位
KSE(kernel sheduling entity)内核调度所处理的对象

进程和线程都是KSE

略略略结合线程

linux/unix编程手册-29(线程)

  • 共享数据
  • 创建线程比创建进程快10倍多(通过clone())
Pthread API
类型描述
pthread_t线程ID
pthread_mutex_t互斥对象
pthread_mutexattr_t互斥属性对象
pthread_cond_t条件变量
pthread_condattr_t条件变量的属性对象
pthread_key_t线程持有数据的键
pthread_once_t一次性初始化控制上下文
pthread_attr_t线程的属性对象

在线程中errno被标记成宏,展开后是返回一个可修改左值的函数

编译Pthread API程序

设置cc -pthread

创建线程
#include<pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start) (void *), void *arg);
复制代码
  • 新线程通过调用start(arg)开始
  • start的返回值不应等于PTHREAD_CANCELED,会被pthread_join捕获?,不应在线程栈中分配start的返回值
  • pthread参数保存线程的唯一标识
  • attr指定了新线程的各种属性
终止线程
  • start函数执行return并返回返回值
  • 线程调用pthread_exit()
  • 调用pthread_cancel()取消线程
  • 任意线程调用了exit(),或主线程执行了return 语句,导致进程中所有线程立即终止
#include<pthread.h>

void pthread_exit(void *retval);
复制代码
  • retval指定了线程的返回值,同start返回值一样不应再线程栈中分配
  • 若主线程调用pthread_exit则其他线程继续运行
获取线程
# include<pthread.h>

pthread_t pthread_self(void);

int pthread_equal(pthread_t t1, pthread_t t2);

复制代码

linux中线程ID所有进程中都唯一,其他不一定

连接已终止线程
#include<pthread.h>
int pthread_join(pthread_t thread, void **retval);
// 等待thread标志的线程终止,如果已经终止立刻返回
// 传入之前连接过的thread 会导致未定义结果
复制代码

pthread_joinwaitpid比较

  • 线程之间没有层级关系,ex:线程A 创建B,B创建C,A可以pthread_joinC, C可以pthread_joinA, fork之后只有父进程能wait子进程
  • 无法连接任意进程,已不能以非阻塞方式连接

如果线程既没有分离也没有连接,会产生僵尸线程

线程的分离
  • 不关心线程的返回,希望在线程终止时自动清理并且移除
  • 一旦线程detach,不能再join
  • 其他线程调用了exit()或者主线程执行return,遭分离的线程还是会受到影响
#inlcude<pthread.h>

int pthread_detach(pthread_t thread);
复制代码

linux/unix编程手册-30(线程同步)

互斥量(保护共享变量的访问)

静态分配互斥量

#include<pthread.h>

static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);
复制代码
  • 如果发起pthread_mutex_lock()的进程已然将目标互斥量锁定
    • 线程死锁(linux默认)
    • 返回EDEADLK错误
  • pthread_mutex_trylock()不会阻塞,失败会返回EBUSY
  • pthread_mutex_timedlock() 可以传参数abstime, 超时后返回ETIMEOUT
  • 加锁减锁的操作比之间代码逻辑操作小的多时,性能影响较小,比文件锁,信号量加减锁性能要高
  • 互斥量实现采用了机器语言级原子操作,内存中执行
死锁
  • 相同顺序对互斥量加锁
  • 使用pthread_mutex_lock()锁定第一个,之后用pthread_mutex_trylock()锁定之后,如果锁定失败,释放所有互斥量在重来,效率较低

动态初始化互斥量

#include<pthread.h>

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t * attr);

int pthread_mutex_destroy(pthread_mutex_t *mutex);

void pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);
复制代码

必须动态初始化的case

  • 互斥量动态分配于堆中
  • 互斥量在栈中自动分配
  • 初始化由静态分配,不使用默认属性的互斥量
  • 动态分配的必须destory,在堆free或是函数栈消失前释放

互斥量的类型(pthread_mutexattr_t)

  • PTHREAD_MUTEX_NORMAL 不具有死锁检测,对自己锁定互斥量加锁则死锁,对未锁定或其他线程锁定的互斥量解锁会导致不确定结果(Liunx上都会成功)
  • PTHREAD_MUTEX_ERRORCHECK 以上三种情况都会犯回错误,但运行比前者慢,可作为调试工具
  • PTHREAD_MUTEX_RECURSIVE 同一线程每次加锁锁计数器会加一,解锁到0其他线程才能加锁,对于后两种异常,解锁都会失败
条件变量(通知状态的改变)

允许一个线程就某个共享资源的状态变化通知其他线程,并让其他线程等待这一通知

静态分配条件变量

#include<pthread.h>

static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

//唤醒任意单个线程
int pthread_cond_signal(pthread_cond_t *cond);

//唤醒所有线程
int pthread_cond_broadcast(pthread_cond_t *cond);

//解锁互斥量mutex -> 阻塞调用线程,直到另一线程cond发出型号->重新锁定mutex (如果在2->3中发现mutex被锁了,会再次休眠)
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
复制代码

pthread_cond_wait的操作

  • 解锁互斥量mutex
  • 阻塞调用线程,直到另一线程cond发出信号
  • 重新锁定mutex

如果在2->3中发现mutex被锁了,会再次休眠

阻塞时其他线程获得CPU使用权,而不会盲轮训之类的浪费资源

#include<pthread.h>

static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;

s = pthread_mutex_lock(&mtx);

while (...){
    s = pthread_cond_wait(&cond, &mtx);
}
s = pthread_mutex_unlock(&mtx); 
复制代码

之所以while

  • 其他线程抢先
  • 宽松原则
  • 虚假唤醒

动态分配条件变量

#include<pthread.h>

int pthread_cond_init(pthread_cond_t *mutex, const pthread_condattr_t * attr);

int pthread_cond_destroy(pthread_cond_t *mutex);
复制代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值