Linux进程下


一. 信号

Ⅰ. 信号的基本概念

信号

信号产生

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

linux查看信号种类man 7 signal

Ⅱ. kill函数

man 2 kill

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

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

#include <signal.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

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 <signal.h>
int raise(int sig);
#include <signal.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

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

Ⅳ. alarm函数

定时给自己发送SIGALRM。

man 2 alarm

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
  • 参数秒,表示几秒后给自己发送信号
  • 返回值,闹钟剩余秒数
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

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

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

Ⅴ. 捕获信号函数

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

man 2 signal

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

man 2 sigaction

#include <signal.h>
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 <sys/time.h>
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 <signal.h>
#include <stdio.h>
#include <sys/time.h>
#include <unistd.h>

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;
}

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

Ⅶ. 信号集函数

man 3 sigemptyset

#include <signal.h>
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 <signal.h>
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 <signal.h>
int sigpending(sigset_t *set);

利用SIGCHLD回收子进程

#include <signal.h>
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>

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;
}

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

二. 守护进程

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

创建守护进程步骤:

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

创建守护进程:

#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

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下的进程就算粗略学习完了,绝知此事要躬行,还有很多要学习的。


文章作者: incipe
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 incipe !
评论
 上一篇
几个重要的函数 几个重要的函数
几个重要的函数Ⅰ. 绝对值函数$$ f(x)=\left|x\right| = \left{ \begin{aligned} -x, x < 0 \ x, x \geq 0 \end{aligned} \right. = \left{
2020-07-30
下一篇 
Linux线程 Linux线程
一. 线程pthread不是linux下默认的库,有可能需要安装。 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev 通过man pthread_c
2020-06-23
  目录