Linux进程(下)

一. 信号

Ⅰ. 信号的基本概念

信号

信号产生

  • 按键产生,Ctrl + c、Ctrl + z
  • 调用函数,kill、raise、abort
  • 定时器,alarm、setitimer
  • 命令产生,kill
  • 硬件异常,段错误,浮点型错误,总线错误,SIGPIPE。

linux 查看信号种类man 7 signal

Ⅱ. kill 函数

man 2 kill

#include 
#include 
int kill(pid_t pid, int sig);
  • pid > 0, 要发送进程 ID;
  • pid = 0,代表当前调用进程组内所有进程;
  • pid = -1,代表有权限发送的所有进程;
  • pid < 0,代表-pid 对应的组内所有进程;
  • sig 对应信号 。

简单使用,用子进程结束父进程。

#include 
#include 
#include 
#include 
#include 

int main() {
  int i;
  for (i = 0; i < 5; ++i) {
    pid_t pid = fork();
    if (pid == 0) {
      break;
    }
  }
  if (i == 2) {
    printf("I am a child process\n");
    sleep(5);
    kill(getppid(), SIGKILL);
    while (1) {
      ;
    }
  } else if (i == 5) {
    printf("I am a father process\n");
    sleep(1);
  }
  return 0;
}

Ⅲ. raise 函数

给自己发信号。

man raise

#include 
int raise(int sig);
#include 
#include 
#include 
#include 
#include 

int main() {
  sleep(1);
  raise(SIGKILL);
  return 0;
}

Ⅳ. alarm 函数

定时给自己发送 SIGALRM。

man 2 alarm

#include 
unsigned int alarm(unsigned int seconds);
  • 参数秒,表示几秒后给自己发送信号
  • 返回值,闹钟剩余秒数
#include 
#include 
#include 
#include 

int main() {
  alarm(6);
  for (int i = 0; i < 6; ++i) {
    printf("i = %d\n", i);
    sleep(1);
  }
  return 0;
}

程序运行可以看到,的确在第六秒发送了信号。

Linux进程(下)_第1张图片

Ⅴ. 捕获信号函数

当有信号的时候调用函数。

man 2 signal

#include 
typedef void (*sighandler_t)(int); // 函数指针,返回值void,参数类型int
sighandler_t signal(int signum, sighandler_t handler);

man 2 sigaction

#include 
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
  • 参数
    • signum,传入的信号
    • act,传入的动作
    • oldact,原动作

sigaction 结构体

struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask; // 临时屏蔽的信号集
               int        sa_flags; // 0使用第一个函数指针, SA_SIGINFO使用第二个函数指针
               void     (*sa_restorer)(void); // 无效
           };

Ⅵ. setitimer 函数

周期性的发送信号。

man 2 setitimer

#include 
int setitimer(int which, const struct itimerval *new_value, truct itimerval *old_value)
  • 参数
    • which
      • ITIMER_REAL,自然定时,信号 SIGALRM ;
      • TIMER_VIRTUAL,进程执行时间,信号 SIGVTALRM;
      • ITIMER_PROF,进程执行时间+CPU 调度时间,信号 SIGPROF。
    • new_value
      • 传入参数,要设置的闹钟时间。
    • old_value
      • 传出参数,得到原来的闹钟时间。

struct itimerval 结构体定义

struct itimerval {
               struct timeval it_interval; /* Interval for periodic timer 周期性时间设置*/
               struct timeval it_value;    /* Time until next expiration 下次闹钟产生*/
           };

struct timeval 结构体定义

struct timeval {
               time_t      tv_sec;         /* seconds */
               suseconds_t tv_usec;        /* microseconds 微秒*/
           };

使用:

#include 
#include 
#include 
#include 

void catch_signal(int num) { printf("catch a %d signal\n", num); }

int main() {
  signal(SIGALRM, catch_signal);
  struct itimerval myit = {{3, 0}, {5, 0}};// it_interval.tv_sec = 3, it_interval.tv_usec = 0...
  setitimer(ITIMER_REAL, &myit, NULL);
  while (1) {
    printf("...\n");
    sleep(1);
  }
  return 0;
}

Linux进程(下)_第2张图片

通过运行结果,我们可以看到,程序的确是按照我们的预期跑的,5 秒后闹钟产生,之后每隔 3 秒发送一次信号。

Ⅶ. 信号集函数

man 3 sigemptyset

#include 
int sigemptyset(sigset_t *set); //清空信号集函
int sigfillset(sigset_t *set); // 填充信号集
int sigaddset(sigset_t *set, int signum); //添加某个信号到信号集
int sigdelset(sigset_t *set, int signum); //从集合中删除某个信号
int sigismember(const sigset_t *set, int signum); //是否为集合里的成员

sigismember()返回 1 表示 signum 在集合中,0 表示不在,-1 失败,设置 error。

其余返回 0 表示成功,-1 表示失败,设置 error。

设置阻塞或解除阻塞信号集。

man 3 sigprocmask

#include 
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
  • 参数
    • how
      • SIG_BLOCK 设置阻塞;
      • SIG_UNBLOCK 解除阻塞;
      • SIG_SETMASK 设置 set 为新的阻塞信号集。
    • set 传入的信号集
    • oset 传出参数,旧的信号集
  • 返回 0 表示成功,-1 失败,设置 error。

获取未决信号集。

man 3 sigpending

#include 
int sigpending(sigset_t *set);

利用 SIGCHLD 回收子进程

#include 
#include 
#include 
#include 

void catch_sig(int num) {
  pid_t wpid = waitpid(-1, NULL, WNOHANG);
  if (wpid > 0) {
    printf("wait child %d ok\n", wpid);
  }
}

int main() {
  int i = 0;
  pid_t pid;
  // 在创建子进程之前屏蔽SIGCHLD信号
  sigset_t myset, oldset;
  sigemptyset(&myset);
  sigaddset(&myset, SIGCHLD);
  // oldset保护现场,阻塞信号SIGCHLD,得到阻塞前的信号SIGCHLD
  sigprocmask(SIG_BLOCK, &myset, &oldset);
  for (i = 0; i < 10; ++i) {
    pid = fork();
    if (pid == 0) {
      break;
    }
  }
  if (i == 10) {
    struct sigaction act;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    act.sa_handler = catch_sig;
    sigaction(SIGCHLD, &act, NULL);
    sigprocmask(SIG_SETMASK, &oldset, NULL);
    while (1) {
      sleep(1);
    }
  } else if (i < 10) {
    printf("I am %d child, pid = %d\n", i, getpid());
    sleep(i);
  }
  return 0;
}

Linux进程(下)_第3张图片

通过ps -ajx可以看到,的确杀死了子进程。

二. 守护进程

个人理解守护进程,不用占用终端,程序可以一直进行下去 ,守护进程一般以 d 结尾,linux 下有 systemd 系统工具来启动守护进程。

创建守护进程步骤:

  1. 创建子进程,kill 父进程;
  2. 子进程调用 setsid 当会话组长和进程组长,失去终端;
  3. 忽略 SIGHUP 信号;
  4. 切换工作目录;
  5. 用 umask 设置掩码;
  6. 关闭文件描述符 0、1、2,避免资源浪费;
  7. 执行核心逻辑;
  8. 守护进程退出,通过kill pid

创建守护进程:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int i = 0;

void print(int num) { printf("i = %d\n", i++); }

int main() {
  // 创建子进程,父进程退出
  pid_t pid = fork();
  if (pid > 0) {
    exit(1);
  }
  // 当会长
  setsid();
  // 设置掩码
  umask(0);
  // 切换目录
  chdir(getenv("HOME"));
  // 关闭文件描述符
  // close(1);
  // close(2);
  // close(3);
  // 执行核心逻辑
  struct itimerval myit = {{1, 0}, {1, 0}};
  setitimer(ITIMER_REAL, &myit, NULL);
  struct sigaction act;
  act.sa_flags = 0;
  sigemptyset(&act.sa_mask);
  act.sa_handler = print;
  sigaction(SIGALRM, &act, NULL);
  while (1) {
    sleep(1);
  }
  // 退出
  return 0;
}
// 每隔一秒打印一个数字,为了方便,没有关闭文件描述符

Linux进程(下)_第4张图片

可以看到,守护进程创建成功。

三. 总结

本来还打算用信号和线程写一个睡眠排序,发现好像有点困难,就放弃了。

linux 下的进程就算粗略学习完了,绝知此事要躬行,还有很多要学习的。

你可能感兴趣的:(#,Linux,#,C/C++)