Linux 进程复制与替换

主函数

主函数的参数

int main (int argc, char * argv[ ], char * envp [ ])

  • argc 参数个数

  • argv 参数内容

  • envp 环境变量

    用法:

    lcx@lcx-virtual-machine:~/mycode/10.20$ vi test.c
      1 #include<stdio.h>
      2 int main(int argc,char *argv[],char *envp[])
      3 {
      4     printf("argc=%d\n",argc);
      5     for(int i=0;i<argc;i++)
      6     {
      7         printf("argv[%d]=%s\n",i,argv[i]);
      8     }
      9     for(int i=0;envp[i]!=NULL;i++)
     10     {
     11         printf("envp[%d]=%s\n",i,envp[i]);
     12     }
     13     exit(0);
     14 }
     15 
    lcx@lcx-virtual-machine:~/mycode/10.20$ gcc -o test test.c
    lcx@lcx-virtual-machine:~/mycode/10.20$ ls
    add.c  add.o  foo.h  libfoo.so  main  main.c  max.c  max.o  test  test.c  test.o
    lcx@lcx-virtual-machine:~/mycode/10.20$ ./test hello lcx yyf
    argc=4
    argv[0]=./test
    argv[1]=hello
    argv[2]=lcx
    argv[3]=yyf
    envp[0]=SHELL=/bin/bash
    envp[1]=SESSION_MANAGER=local/lcx-virtual-machine:@/tmp/.ICE-unix/1955,unix/lcx-virtual-machine:/tmp/.ICE-unix/1955
    envp[2]=QT_ACCESSIBILITY=1
    envp[3]=COLORTERM=truecolor
    envp[4]=XDG_CONFIG_DIRS=/etc/xdg/xdg-ubuntu:/etc/xdg
    envp[5]=XDG_MENU_PREFIX=gnome-
    envp[6]=GNOME_DESKTOP_SESSION_ID=this-is-deprecated
    envp[7]=GTK_IM_MODULE=ibus
    envp[8]=LANGUAGE=zh_CN:zh:en_US:en
    envp[9]=QT4_IM_MODULE=ibus
    envp[10]=LC_ADDRESS=zh_CN.UTF-8
    envp[11]=GNOME_SHELL_SESSION_MODE=ubuntu
    envp[12]=LC_NAME=zh_CN.UTF-8
    envp[13]=SSH_AUTH_SOCK=/run/user/1000/keyring/ssh
    envp[14]=XMODIFIERS=@im=ibus
    ​

输出:Printf

lcx@lcx-virtual-machine:~/mycode/10.22$ vi main.c 
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>//linux特有的头文件
  4 int main()
  5 {   
  6     printf("hello");
  7     sleep(3);//阻塞3秒
  8     exit(0);//返回值为0程序退出成功
  9 }
lcx@lcx-virtual-machine:~/mycode/10.22$ gcc -o main main.c
lcx@lcx-virtual-machine:~/mycode/10.22$ ./main
hellolcx@lcx-virtual-machine:~/mycode/10.22$ 

此时会发现程序先阻塞三秒才进行了输出hello。这是为什么呢?

printf 函数并不会直接将数据输出到屏幕,而是先放到缓冲区中,只有一下三种情况满 足,才会输出到屏幕。

  • 缓冲区满

  • 强制刷新缓冲区 fflush 或者加\n

  • 程序结束时

    改进:

    1.  #include <stdio.h>
    2.  #include <stdlib.h>
    3.  #include <unistd.h>
    4.  int main()
    5.  {
    6.  printf("hello");//printf("hello\n");
    7.  //fflush(stdout);
    8.  sleep(3);
    9.  exit(0);
    10. }

复制进程(fork)

fork 方法

pid_t fork(void);

  • fork 函数会新生成一个进程,调用 fork 函数的进程为父进程,新生成的进程为子进程

  • 函数返回类型 pid_t 实质是 int 类型, 在父进程中返回值为子进程的 id号,在子进程中返回 0,失败返回-1。

  • 复制进程时会将进程的pcb及实体都进行复制。

  • 父进程与子进程并发进行。

  • 复制进程中含有写时拷贝技术,其用来提高fork复制的效率

写实拷贝技术

即子进程在拷贝父进程时,当某些物理页当前不需要修改时候,在底层实质上是将该物理页共享给了子进程,只有当需要修改的时候才会进行复制该物理页。以此提高复制的效率。

例如:

lcx@lcx-virtual-machine:~/mycode/10.23$ vi main.c
​
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<string.h>
  4 #include<unistd.h>
  5 #include<assert.h>
  6 #include<fcntl.h>
  7 int main()
  8 {
  9     char *s=NULL;
 10     int n=0;
 11     pid_t pid=fork();
 12     assert(pid!=-1);
 13     if(pid==0)
 14     {
 15         s="child";
 16         n=4;
 17     }
 18     else
 19     {
 20         s="parent";
 21         n=7;
 22     }
 23     int i=0;
 24     for(;i<n;i++)
 25     {
 26         printf("pid=%d  ,ppid=%d  ,s=%s\n",getpid(),getppid(),s);
 27         sleep(1);
 28     }
 29     exit(0);
 30 }
 
lcx@lcx-virtual-machine:~/mycode/10.23$ gcc -o main main.c
lcx@lcx-virtual-machine:~/mycode/10.23$ ./main
pid=3987,  ppid=3826,  s=parent
pid=3988,  ppid=3987,  s=child
pid=3987,  ppid=3826,  s=parent
pid=3988,  ppid=3987,  s=child
pid=3987,  ppid=3826,  s=parent
pid=3988,  ppid=3987,  s=child
pid=3988,  ppid=3987,  s=child
pid=3987,  ppid=3826,  s=parent
pid=3987,  ppid=3826,  s=parent
pid=3987,  ppid=3826,  s=parent
pid=3987,  ppid=3826,  s=parent
lcx@lcx-virtual-machine:~/mycode/10.23$ 
​

fork笔试题

例:

1.循环中fork()

lcx@lcx-virtual-machine:~/mycode/10.26$ vi main.c
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 int main()
  5 {
  6     for(int i=0;i<2;i++)
  7     {
  8         fork();
  9         printf("A\n");
 10     }
 11 }
~      
lcx@lcx-virtual-machine:~/mycode/10.26$ gcc -o main main.c
lcx@lcx-virtual-machine:~/mycode/10.26$ ./main
A
A
A
A
A
A
​
//题2
lcx@lcx-virtual-machine:~/mycode/10.26$ vi main.c
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 int main()
  5 {
  6     for(int i=0;i<2;i++)
  7     {
  8         fork();
  9         printf("A");//此时在fork时会将父进程缓冲区中的A也复制一份,因此会输出8个A
 10     }
 11 }
~                                                
lcx@lcx-virtual-machine:~/mycode/10.26$ gcc -o main main.c
lcx@lcx-virtual-machine:~/mycode/10.26$ ./main
AAAAAAAA

2.fork && fork以及fork || fork

lcx@lcx-virtual-machine:~/mycode/10.26$ vi main.c
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 int main()
  5 {
  6     fork()||fork();//fork()&&fork(),||父进程进行一次fork,子进程一次。&&父进程两次fork,子进程一次,会3个A
  7     printf("A\n");
  8 }
~    
lcx@lcx-virtual-machine:~/mycode/10.26$ gcc -o main main.c
lcx@lcx-virtual-machine:~/mycode/10.26$ ./main
A
A
A

僵死进程

概念

僵死进程概念:子进程先于父进程结束,父进程没有调用 wait 获取子进程退出码,子进程变成僵死进程此时虽然子进程的进程实体被收回,但内核并没有收回子进程的PCB,占内存空间,软件层面系统pid在被消耗。

代码展示:父进程:parent,子进程:child.

lcx@lcx-virtual-machine:~/mycode/10.26$ vi main.c
lcx@lcx-virtual-machine:~/mycode/10.26$ gcc -o main main.c
lcx@lcx-virtual-machine:~/mycode/10.26$ ./main
s=parent
s=child
s=parent
s=child
s=child
s=parent
s=parent
s=parent
s=parent
s=parent
s=parent
s=parent
s=parent
lcx@lcx-virtual-machine:~/mycode/10.26$ ./main &
[1] 73184
lcx@lcx-virtual-machine:~/mycode/10.26$ s=parent
s=child
s=child
s=parent
ps
    PID TTY          TIME CMD
  16857 pts/0    00:00:00 bash
  73184 pts/0    00:00:00 main
  73185 pts/0    00:00:00 main
  73186 pts/0    00:00:00 ps
lcx@lcx-virtual-machine:~/mycode/10.26$ s=child
s=parent
pss=parent
​
    PID TTY          TIME CMD
  16857 pts/0    00:00:00 bash
  73184 pts/0    00:00:00 main
  73185 pts/0    00:00:00 main <defunct>//僵死进程
  73187 pts/0    00:00:00 ps
lcx@lcx-virtual-machine:~/mycode/10.26$ s=parent
s=parent
s=parent
s=parent
s=parent
s=parent
​

如上 ,当子进程结束后,并没有消失,仍然可以在系统中观测到,但此时 子进程其实已经运行结束了,此时子进程的状态被称为僵死状态,系统把处于该类状态的进 程称为僵死进程。 如果父进程先结束,子进程最后是不会变为僵死进程的。

处理僵死进程

如何处理僵死进程:

1.父进程通过调用 wait()完成。

lcx@lcx-virtual-machine:~/mycode/10.26$ vi main.c
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<assert.h>
  5 #include<string.h>
  6 #include<sys/wait.h>
  7 
  8 int main()
  9 {
 10     int n=0;
 11     char *s=NULL;
 12     pid_t pid=fork();
 13     assert(pid!=-1);
 14     if(pid==0)
 15     {
 16         n=3;
 17         s="child";
 18     }
 19     else
 20     {
 21         n=10;
 22         s="parent";
 23         int val=0;
 24         wait(&val);获得子进程的退出码,此时将导致父进程阻塞。
 25         /*if ( WIFEXITED(val) )
 26          {
 27           printf("val=%d\n",WEXITSTATUS(val));
 28          }
 29          将退出码打印出来
 30 
 31 
 32 
 33          */
 34     }
 35     for(int i=0;i<n;i++)
 36     {   
 37         printf("s=%s\n",s);
 38         sleep(1);
 39     }
 40     exit(3);
 41 }
 42 
​

2.Init 进程收养孤儿进程

孤儿进程:当父进程结束后,子进程失去了父进程,称为孤儿进程。

在以前的版本中,Init进程为系统fork出来的第一个进程,pid为1。现在内核版本不同,其pid可能不一样。

当系统出现孤儿进程时,系统会让Init进程去收养孤儿进程,在Init进程中存在获得子进程退出码的操作wait()。此时子进程便消失了。

因此,当父进程先结束,子进程一定不会变成僵死进程

文件操作的系统调用

系统调用

系统调用在内核中实现,进行系统调用时,会从用户态切换到内核态,这是因为用户调用内核中的系统调用实际上是不被允许的,没有办法去直接调用,为了使用户空间可以告诉内核完成某些系统调用,就提出了系统调用号,即每个系统调用功能都有其对应的编号。

在程序运行到系统调用时,此时出现断点,处理器进行现场保护,通过头文件拿到该系统调用的编号,再将该编号存入exa寄存器中,传给内核,内核接收后查找系统调用表,找到其对应的系统调用内核代码,例如:打开文件,内核将文件打开,创建文件结构体,最后将结果又存入exa,返回出去,此时处理器拿到数据进行现场回复,程序恢复执行。

一些库函数底层便是通过系统调用进行。库函数的调用的开销要小于系统调用。

因此抽象来说:命令(ls)-》库函数-》系统调用-》内核-》硬件。

例题:问:程序打印结果:

lcx@lcx-virtual-machine:~/mycode/10.31$ vi main.c
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 #include<assert.h>
  6 int main()
  7 {
  8     printf("A");
  9     write(1,"B",1);
 10     fork();
 11     exit(0);
 12 }
 
lcx@lcx-virtual-machine:~/mycode/10.31$ gcc -o main main.c
lcx@lcx-virtual-machine:~/mycode/10.31$ ./main
BAA

因为库函数printf会将A输出到了缓冲区中,而系统调用write直接将B输出到屏幕上,此时fork(),会将缓冲区中的A也复制过去,程序结束父子进程都会在输出A。

文件操作的系统调用

open ,read ,write ,close 。系统的调用实现在内核中。

  1. int open(const char *pathname, int flags); 用于打开一个已存在的文件。

  2. int open(const char* pathname, int flags,mode_t mode); 用于新建一个文件。参数如下:

    • pathname:将要打开的文件路径和名称

    • flags : 打开标志,如

      1. O_WRONLY 只写打开 ;

      2. O_RDONLY 只读打开

      3. O_RDWR 读写方式打开

      4. O_CREAT 文件不存在则创建

      5. O_APPEND 文件末尾追加

      6. O_TRUNC 清空文件,重新写入

    • mode: 权限 如:“0600”

    • 返回值:为文件描述符,即存在于PCB中的文件表中该文件的下标。

  3. ssize_t read(int fd, void* buf, size_t count); 读文件

    • fd 对应打开的文件描述符

    • buf 存放数据的空间

    • count 计划一次从文件中读多少字节数据

    • 返回值:为实际读到的字节

  4. ssize_t write(int fd, const void* buf,size_t count); 写文件

    • fd 对应打开的文件描述符

    • buf 存放待写入的数据

    • count 计划一次向文件中写多少数据

  5. int close(int fd) ; 关闭文件

    • d 要关闭的文件描述符

应用:

1.打开 写入 读取 关闭 一个文件。

lcx@lcx-virtual-machine:~/mycode/10.26$ vi main.c
​
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<assert.h>
  5 #include<string.h>
  6 #include<sys/wait.h>
  7 #include<fcntl.h>
  8 
  9 int main()
 10 {
 11     int fd=open("file.txt",O_WRONLY|O_CREAT,0600);//以只写打开一个需要创建的文件,权限可读可写
 12     assert(fd!=-1);
 13     write(fd,"hello",5);//写入hello
 14     close(fd);//关闭文件
 11     int fd=open("file.txt",O_RDONLY);  //以直读方式打开一个文件
 12     assert(fd!=-1);
 13     char buff[128]={0};           //文件中的读出的数据存入的空间
 14     int n=read(fd,buff,127);      //读取文件,buff末尾为0
 15     printf("buff=%s\n",buff);     //打印
 15     printf("n=%d\n",n);
 16     close(fd);                    //关闭
 17 }
​
 15 }
~     
​
lcx@lcx-virtual-machine:~/mycode/10.26$ gcc -o main main.c
lcx@lcx-virtual-machine:~/mycode/10.26$ ./main
buff=hello
n=5
lcx@lcx-virtual-machine:~/mycode/10.26$ ls
file.txt  main  main.c
lcx@lcx-virtual-machine:~/mycode/10.26$ cat file.txt
hellolcx@lcx-virtual-machine:~/mycode/10.26$ 
​

2.复制文件。

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<assert.h>
  5 #include<string.h>
  6 #include<sys/wait.h>
  7 #include<fcntl.h>
  8 
  9 int main()
 10 {
 11     int fdr=open("file.txt",O_RDONLY);
 12     int fdw=open("file1.txt",O_WRONLY|O_CREAT,0600);
 13     assert(fdr!=-1&&fdw!=-1);
 14     int n=0;//记录读取出来的字符个数
 15     char buff[1024]={0};//每次拷贝1024个字节
 16     while((n=read(fdr,buff,1024))>0)//当n=0时便读到了末尾
 17     {
 18         write(fdw,buff,n);
 19     }
 20     close(fdr);
 21     close(fdw);
 22 }
​

描述符

进程描述符,文件描述符,文件偏移量的关系

进程描述符:在操作系统中将描述进程实体属性信息的结构称为进程控制块,进程控制块在Linux系统被称为进程描述符,是一个结构体,也称PCB。

文件描述符:在PCB中有一个成员为文件表,用来记录打开的文件,具有上限,注:每个进程启动都默认打开三个文件:stdin ,stdout ,stderr,对应0,1,2;将0,1,2这些下标称为文件描述符。

struct file:每当open内核便会产生一个记录打开的文件的结构体。其中的inode指向一个磁盘上的文件

父子进程对文件操作

当文件在fork之前打开时,在fork的时候会将打开文件的文件描述符也拷贝过去,通过文件描述符父子文件访问的是同struct file,此时便共用了文件偏移量。

  • 子进程可以使用父进程打开的文件。

  • 子进程和父进程同时也共享文件偏移量。

lcx@lcx-virtual-machine:~/mycode/10.26$ vi main.c
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<assert.h>
  5 #include<string.h>
  6 #include<sys/wait.h>
  7 #include<fcntl.h>
  8 
  9 int main()
 10 {
 11     int fd=open("file.txt",O_RDONLY);
 12     assert(fd!=-1);
 13     pid_t pid = fork();
 14     assert(pid!=-1);
 15     if(pid==0)
 16     {
 17         char buff[8]={0};
 18         read(fd,buff,1);
 19         printf("chid buff=%s\n",buff);
 20         sleep(1);
 21         read(fd,buff,1);
 22         printf("chid buff=%s\n",buff);
 23     }
 24     else
 25     {
 26         char buff[8]={0};
 27         read(fd,buff,1);
 28         printf("parent buff=%s\n",buff);
 29         sleep(1);
 30         read(fd,buff,1);
 31         printf("parent buff=%s\n",buff);
 32     }
 33     close(fd);
 34     exit(0);
 35 }
lcx@lcx-virtual-machine:~/mycode/10.26$ gcc -o main main.c
lcx@lcx-virtual-machine:~/mycode/10.26$ ./main
parent buff=a
chid buff=b
chid buff=c
parent buff=d
​

进程替换

exec 系列替换过程:

pcb 使用以前的只进行部分修改,进程实体更换。exec执行成功,会直接从新程序的主函数开始执行,不会执行剩下的代码,只有失败时才会返回错误码接着执行。

exec 系列替换方法:

  • execl :库函数

  • execlp :库函数

  • execle :库函数

  • execv :库函数

  • execvp :库函数

  • execve :系统调用

    前五个底层都是调用第六个。

exec基础的参数:

1.新替换的程序的路径名称。

2.传给新程序主函数的第一个参数,一般为程序的名字,后面是剩余参数列表,参数个数可变。

3.必须以空指针作为最后一个参数。

4.环境变量。

操作如下

lcx@lcx-virtual-machine:~/mycode/10.23$ vi main.c
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<string.h>
  4 #include<unistd.h>
  5 #include<assert.h>
  6 #include<fcntl.h>
  7 int main(int argc,char*argv[],char*envp[])
  8 {
  9     printf("main pid=%d\n",getpid());
 10     //execl("/usr/bin/ps","ps","-f",(char*)0);//需要绝对路径
 11     //execlp("ps","ps","-f",(char*)0);        //p为环境变量,因此不需要绝对路径
 12     //execle("/usr/bin/ps","ps","-f",(char*)0,envp);//envp 系统环境变量
 13     char *myargv[10]={"ps","-f"};    //为了方便管理给新程序主函数的参数,可将参数放入数组,自动补0为空指针
 14     //execv("/usr/bin/ps",myargv);  //4
 15     //execvp("ps",myargv);          //5
 16     execve("/usr/bin/ps",myargv,envp);//系统调用,前五中底层调用此法
 17    //若exec执行成功,会直接从新程序的主函数开始执行,不会执行剩下的代码,只有失败时才会返回错误码接着执行
返回错误码
 18     printf("execl error\n");
 19     exit(0);    
 20  }
lcx@lcx-virtual-machine:~/mycode/10.23$ gcc -o main main.c
lcx@lcx-virtual-machine:~/mycode/10.23$ ./main
main pid=3560
UID          PID    PPID  C STIME TTY          TIME CMD
lcx         3078    3066  0 15:06 pts/0    00:00:00 bash
lcx         3560    3078  0 15:19 pts/0    00:00:00 ps -f

信号

  • 信号是系统响应某个条件而产生的事件,进程接收到信号会执行相应的操作。

  • 与信号有关的系统调用在“signal.h”头文件中有声明

  • 进程可以捕获来自其他进程及内核产生的信号,在进程捕获信号后会进行响应信号(即做出相应的处理)。

  • 信号在内核中进行了编码,不同的编码含有不同的含义

  • 在pcb中存在一个成员int型,32位,用来接收信号,信号传递时先通过 pid 找到对应的进程,再将其pcb中的信号成员中二进制位第n(所传递的信号编号)位0变为1。

  • 在信号响应时,根据PCB中信号成员的位数在系统中的编码表找到对应的方式在内核中进行调用。

    1. #define SIGHUP 1
    2. #define SIGINT 2 //键盘按下 Ctrl+c 时,会产生该信号,中断程序
    3. #define SIGQUIT 3
    4. #define SIGILL 4
    5. #define SIGTRAP 5
    6. #define SIGABRT 6
    7. #define SIGIOT 6
    8. #define SIGBUS 7
    9. #define SIGFPE 8
    10. #define SIGKILL 9 //该信号的响应方式不允许改变
    11. #define SIGUSR1 10
    12. #define SIGSEGV 11
    13. #define SIGUSR2 12
    14. #define SIGPIPE 13 //读端关闭的描述符,写端写入时产生,该信号会终止程序
    15. #define SIGALRM 14
    16. #define SIGTERM 15 //系统 kill 命令默认发送的信号
    17. #define SIGSTKFLT 16
    18. #define SIGCHLD 17 //子进程结束后,会默认给父进程发送该信号
    19. #define SIGCONT 18
    20. #define SIGSTOP 19
    21. #define SIGTSTP 20
    22. #define SIGTTIN 21
    23. #define SIGTTOU 22
    24. #define SIGURG 23

注:SIGCHLD 子进程结束后,父进程会收到该信号,在默认响应方式下,他的处理方式便是忽略掉

lcx@lcx-virtual-machine:~/mycode/10.26$ vi main.c
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<assert.h>
  5 #include<string.h>
  6 #include<sys/wait.h>
  7 void sig_fun(int sig)
  8 {
  9     printf("sig=%d\n",sig);
  7     wait(NULL);//处理僵死进程,且只有在子进程结束后才执行,父进程不会再被阻塞住
  7 }
  8 int main()
  9 {
 10     int n=0;
 11     char *s=NULL;
 11     singal(SIGCHLD,sig_fun);//自定义子进程结束传给父进程信号,父进程响应方式
 12     pid_t pid=fork();
 13     assert(pid!=-1);
 14     if(pid==0)
 15     {
 16         n=3;
 17         s="child";
 18     }
 19     else
 20     {
 21         n=10;
 22         s="parent";
 25     }
 35     for(int i=0;i<n;i++)
 36     {   
 37         printf("s=%s\n",s);
 38         sleep(1);
 39     }
 40     exit(3);
 41 }
 42 
 

修改信号响应方式

修改信号的响应方式 – signal()

  #include <signal.h>
  typedef void (*sighandler_t)(int);
  sighandler_t signal(int signum, sighandler_t handler);

信号相应的三种方式

  • SIG_IGN 忽略

  • SIG_DFL 默认

  • 自定义信号处理函数

例:将SIGINT终止信号,修改成打印

lcx@lcx-virtual-machine:~/mycode/11.2$ vi main.c
​
​
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 #include<assert.h>
  6 #include<signal.h>
  7 void sig_fun(int sig)//会将SIGINT对应编号传入
  8 {
  9     printf("sig=%d\n",sig);//打印编号
 10 }
 11 
 12 int main()
 13 {
 14     signal(SIGINT,sig_fun);//程序与系统制定协议,当接收到SIGINT信号时,跳转到sig_fun函数
 15     while(1)
 16     {
 17         printf("hello\n");
 18         sleep(1);
 19     }
 20 }
~      
    
lcx@lcx-virtual-machine:~/mycode/11.2$ gcc -o main main.c
lcx@lcx-virtual-machine:~/mycode/11.2$ ./main
hello
hello
^Csig=2  //此时Ctrl + c 无法终止程序,而变为打印编号
hello
hello
^\退出 (核心已转储)  //通过Ctrl + / 终止
​

2.多次修改,第一次SIGINT打印,第二次终止程序

lcx@lcx-virtual-machine:~/mycode/11.2$ vi main.c
​
​
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 #include<assert.h>
  6 #include<signal.h>
  7 void sig_fun(int sig)//会将SIGINT对应编号传入
  8 {
  9     printf("sig=%d\n",sig);//打印编号
 10     signal(sig,SIG_DFL);  //重新进行约定,当再次收到SIGINT信号时,响应方式改为默认。
 10 }
 11 
 12 int main()
 13 {
 14     signal(SIGINT,sig_fun);//程序与系统制定协议,当接收到SIGINT信号时,跳转到sig_fun函数
 15     while(1)
 16     {
 17         printf("hello\n");
 18         sleep(1);
 19     }
 20 }
~      
    
lcx@lcx-virtual-machine:~/mycode/11.2$ gcc -o main main.c
lcx@lcx-virtual-machine:~/mycode/11.2$ ./main
hello
hello
^Csig=2  //此时Ctrl + c 无法终止程序,而变为打印编号
hello
hello
^C       //第二次默认处理方法,将程序终止掉
lcx@lcx-virtual-machine:~/mycode/11.2$ 
​

发送信号 – kill()

kill() 可以向指定的进程发送指定的信号:

  • int kill(pid_t pid, int sig);

  • pid > 0 指定将信号发送个那个进程

  • pid == 0 信号被发送到和当前进程在同一个进程组的进程

  • pid == -1 将信号发送给系统上有权限发送的所有的进程

  • pid < -1 将信号发送给进程组 id 等于 pid 绝对值,并且有权限发送的所有的进程。

  • sig 指定发送信号的类型。

例如:

1.主函数传入两个参数,包括目标进程的pid,

lcx@lcx-virtual-machine:~/mycode/11.2$ vi mykill.c
​
​
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 #include<assert.h>
  6 #include<signal.h>
  7 int main(int argc,char*argv[]) //kill()中需要目标进程的pid,因此在主函数中当参数传入
  8 {
  9     if(argc!=2)  //排除少传多传参数情况。
 10     {
 11         printf("mykill arg err\n");
 12         exit(0);
 13     }
 14     int pid=0;//用来保存pid
 15     sscanf(argv[1],"%d",&pid);//主函数传进来的pid为字符串,调用函数将其转化为整型值
 16     if(kill(pid,SIGINT)==-1)//发送信号,pid为目标进程,SIGINT为信号类型,若返回值为-1,发送失败
 17     {
 18         printf("kill error\n");
 19     }
 20     exit(0);
 21 }
~                                                                              
~                                                                              
~                                                                              
lcx@lcx-virtual-machine:~/mycode/11.2$ gcc -o mykill mykill.c
lcx@lcx-virtual-machine:~/mycode/11.2$ ls
main  main.c  mykill  mykill.c
lcx@lcx-virtual-machine:~/mycode/11.2$ ./mykill
mykill arg err
lcx@lcx-virtual-machine:~/mycode/11.2$ ./mykill 4116 //终止了目标程序
   

2.主函数传入两个参数,包括目标进程的pid,以及传递的信号类型

lcx@lcx-virtual-machine:~/mycode/11.2$ vi mykill.c
​
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 #include<assert.h>
  6 #include<signal.h>
  7 int main(int argc,char*argv[])//参数包括目标进程pid,信号类型
  8 {
  9     if(argc!=3)
 10     {
 11         printf("mykill arg err\n");
 12         exit(0);
 13     }
 14     int pid=0;
 15     int sigh=atoi(argv[2]);   //字符串转整形
 16     sscanf(argv[1],"%d",&pid);//
 17     if(kill(pid,sigh)==-1)    //信号传递
 18     {
 19         printf("kill error\n");
 20     }
 21     exit(0);
 22 }
~  
lcx@lcx-virtual-machine:~/mycode/11.2$ gcc -o mykill mykill.c
lcx@lcx-virtual-machine:~/mycode/11.2$ ./mykill 4116 2    //

注:等同于之前的 kill -n pid,当n=9时,一定可以终止,因为9号对应的信号没法用signal更改,只能以默认方式响应。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值