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

局限性linux

  • 没法等待某个特定子进程完成
  • 没法非阻塞等待
  • 只能检测终止,对于中止或其余情况没法检测

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处理shell

  • 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()以前,子进程就已经终止bash

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

SIGCHLD信号

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

若是每次收到SIGCHLD的时调用wait()由于在调用时对同一信号(SIGCHLD)要么忽略要么阻塞,阻塞的话标准信号之会保留一次,可能会遗漏SIGCHLD信号。工具

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

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

一些特殊spa

  • 一般子进程中止也会向父进程发送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()unix

执行时关闭标志(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);
复制代码
相关文章
相关标签/搜索