三系统编程001_linux服务器

三、系统(环境)编程

1、进程控制

1.程序进程

程序:编译好的二进制文件
进程:执行着的程序

  • 程序员角度:运行一系列指令的过程
  • 操作系统角度: 分配系统资源的基本单位
    区别:
    • 程序占用磁盘,不占用系统资源
    • 进程占用系统资源
    • 一个程序对应多个进程
    • 一个进程只能对应一个程序
    • 进程有生命周期

单道程序(DOS系统)

进程的状态转化

cpu和mmu(memory manage units)
mmu:

  • 1.控制内存到物理地址的映射,(虚拟出更大的内存)
  • 2.设置内存的访问级别(用户空间,内核空间)
    • 用户空间:虚拟地址即使是同一块,也是映射到不同的物理空间(银行账户)
    • 内核空间映射到同一块(进程通信)


2.查看PCB

txdy827@ubuntu:~$ sudo grep -rn "struct task_struct {" /usr/
[sudo] txdy827 的密码: 
#结果:/usr/src/linux-headers-4.15.0-111/include/linux/sched.h:560:struct task_struct {
然后vim 这个文件
/struct task_struct {             //查找到640行,然后光标定在{符号上  使用 % 跳转到结构体结束的位置

结构体内容:

  • 进程id:系统每个进程有唯一的id,在C语言中用pid_t 类型表示,其实就是一个非负整数
  • 进程状态,就绪,运行,挂起,停止等
  • 进程切换时需要保存和恢复的一些CPU寄存器
  • 描述虚拟地址空间的信息
  • 描述控制终端的信息
  • 当前的工作目录,每个进程一个独立的工作目录
  • umasK掩码
  • 文件描述符表,包含很多指向file结构体的指针
  • 和信号相关的信息
  • 用户ID和组ID
  • 会话(session)和进程组
  • 进程可以使用的资源上线
txdy827@ubuntu:~$ ulimit -a  // 查看资源上限
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 15511
max locked memory       (kbytes, -l) 16384
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 15511
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

3.环境变量

写法: key=value 没有空格
命令:env 查看

PATH : 可执行文件的搜索路径
SHELL:当前shell,通常是/bin/bash
TERM:显示终端类型
LANG:语言货币编码时间
HOME:家目录

配置文件: hello.conf
一般放在/etc
/home/pi/etc

函数:获取环境变量

 #include <stdlib.h>
    char *getenv(const char *name);

eg:

#include<stdio.h>
#include<stdlib.h>

int main()
{
        printf("home is [%s]\n",getenv("HOME"));
        return 0;
}
#
txdy827@ubuntu:~/test$ ./a.out
home is [/home/txdy827]

添加一个环境变量:
在~/.bashrc文件中:
export key=val
就好了

4.创建一个新的进程

如果创建父子进程时候,父进程先死了,子进程还没死,则会出现,shell 进程先把自己的进程输出,之后刚才创建的子进程又打印一遍输出,操作系统设计就是只管下一层,当创建的父进程(是shell进程的子进程)死了,shell进程就返回了。
如:

I am a father,I will die!
txdy827@ubuntu:~/test$                I am a child,pid is 2682,ppid is 1234

 #include <sys/types.h>
 #include <unistd.h>
       pid_t fork(void);

返回值:

  • 失败-1
  • 成功:两次返回
    • 父进程返回子进程id
    • 子进程返回0
      获取进程id
#include <sys/types.h>
#include <unistd.h>

pid_getpid(void);//获得pid,当前进程id,
pid_getppid(void);// 获取父进程id

eg
注意,如果创建子进程之前没有\n刷新缓冲区,则会在创建子进程的时候,把子进程会把刚才那个begin—一起创建出来。

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
        printf("begin----------------\n");
        pid_t pid=fork();
        if(pid<0)
        {
                perror("fork err");
                exit(1);
        }
        if(pid==0)
        {
                printf("I am a child, pid is %d,ppid is %d\n",getpid(),getppid());
        }//     if(pid>0)
        else
        {
                printf("I am a parent,child_pid is %d, my_pid is %d,ppid is %d\n",pid,getpid(),getppid());
                sleep(1);  //让父进程等会再死,不然就子进程变成孤儿进程被领养了
        }
        printf("end--------------\n");
        return 0;
}
#结果:
txdy827@ubuntu:~/test$ ./a.out
begin----------------
I am a parent,child_pid is 3363, my_pid is 3362,ppid is 1853
I am a child, pid is 3363,ppid is 3362
end--------------
end--------------

进到一个源文件,在esc之下 输入:,然后 vsp 再一个文件就分屏显示了

我们写的进程的父进程是shell(-bash)进程
最初的父进程是 init进程1号进程
ps aux
ps ajx 追溯血缘关系

杀死进程
kill -9 2980 //杀死2980号进程
kill -l 查看kill信息
SIGSEGV 段错误
SIGBUS 总线错误
SIGFPE 古典错误,除0错误

5.创建多个子进程

grep -v grep :排除关键字grep
创建5gg额子进程

#include<stdio.h>
#include<stdlib.h>

int main()
{
        int i=0;
        int n=5;
        pid_t pid=0;
        for(i=0;i<5;i++)
        {
                pid = fork();
                if(pid==0)
                {
                        printf("I am a son pid is %d,ppid is %d\n",getpid(),getppid());
break;  //子进程退出的口
                }
                else
                {
                        am a father pid is %d,ppid is %d\n",getpid(),getppid());

                }
        }
        while(1){
                sleep(1);
        }
        return 0;
}

eg2创建5个子进程并按照顺序退出

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main()
{
        int i=0;
        int n=5;
        pid_t pid=0;
        for(i=0;i<5;i++)
        {
                pid = fork();
                if(pid==0)
                {
//                      printf("I am a son pid is %d,ppid is %d\n",getpid(),getppid());
break;
                }
                else
                {
//                      printf("I am a father pid is %d,ppid is %d\n",getpid(),getppid());

                }

        }
        sleep(i);
        if(i<5) printf("I am a child,pid is %d,ppid is %d\n",getpid(),getppid());
        else
                printf("I an a patern,pid is%d,ppid is %d\n",getpid(),getppid());

        return 0;
}
#结果:
txdy827@ubuntu:~/test$ ./a.out
I am a child,pid is 2260,ppid is 2259
I am a child,pid is 2261,ppid is 2259
I am a child,pid is 2262,ppid is 2259
I am a child,pid is 2263,ppid is 2259
I am a child,pid is 2264,ppid is 2259
I an a patern,pid is2259,ppid is 1659

6.父子进程关系

刚fork之后,

  • 父子相同处:
    • 全局变量,.data,.text,栈,堆,环境变量,用户ID,宿主目录,进程工作目录,信号处理方式相同
  • 不同之处:
    • 进程ID,fork返回值,父进程id不同,进程运行时间不同,闹钟(定时器)不同,未决信号集不同
      一开始设计是类似复制从0-3G的内存,现在是读时共享,写时复制

7.exec族函数

执行其他程序

#include <unistd.h>
 extern char **environ;

int execl(const char *path, const char *arg, .../* (char  *) NULL */);
//执行程序的时候,使用PATH环境变量,执行程序时不用加路径
int execlp(const char *file, const char *arg, ... /* (char  *) NULL */);

  • file 要执行的程序
  • arg 参数列表
    • 参数列表最后一个需要一个NULL最为结尾,哨兵
    • 参数从argv[0]开始,用要执行的文件名占位,作为第二个参数
  • 返回值
    • 只有失败才返回
      eg:
#include<unistd.h>
#include<stdio.h>

int main()
{
		   execl("/bin/ls","ls","-l","--color=auto",NULL);

        execlp("ls","ls","-l","--color=auto",NULL);  //第二个参数,用要执行的文件名占位置
        
        perror("exec err\n");
        return 0;
}
#结果:
tu:~/test$ ./a.out
总用量 56
-rw-r--r-- 1 txdy827 txdy827   132 7月  29 00:25 06_execlp.c
-rwxr-xr-x 1 txdy827 txdy827  8352 7月  29 00:25 a.out
~  

查看显示的绿色
txdy827@ubuntu:~/test$ alias ls
alias ls=‘ls --color=auto’
调用关系:

shell执行ls等操作,,ls这种程序不出错不返回,所以shell程序,在执行这些之前execl族函数之前先fork()生成一个子进程去执行,然后自己接着自己的做,

8.孤儿进程和僵尸进程

孤儿进程:父亲死了,子进程被init进程领养
僵死进程:子进程死了,父进程没有回收子进程的资源(PCB)

如何回收僵死进程的资源? 杀死父亲,init领养,负责回收

eg:孤儿进程:

#include<stdio.h>
#include<unistd.h>

int main()
{
        pid_t pid = fork();
        if(pid==0)
        {
                while(1){
                        printf("I am a child,pid is %d,ppid is %d\n",getpid(),getppid());
                        sleep(2);
                }
        }else
        {
                printf("I am a parent,pid is %d,ppid is %d\n",getpid(),getppid());
                sleep(4);
                printf("I am a father,I will die!\n");
        }
        return 0;
}
#结果:
txdy827@ubuntu:~/test$ ./a.out
I am a parent,pid is 2681,ppid is 1698
I am a child,pid is 2682,ppid is 2681
I am a child,pid is 2682,ppid is 2681
I am a father,I will die!
txdy827@ubuntu:~/test$ I am a child,pid is 2682,ppid is 1234
I am a child,pid is 2682,ppid is 1234
I am a child,pid is 2682,ppid is 1234

9.wait

函数原型:
#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *wstatus);
作用:

  • 阻塞等待
  • 回收子进程资源
  • 查看死亡原因
    pid_t wait(int * status);
  • status 传出参数
  • 返回值:
    • 成功返回:种植的子进程ID
    • 失败返回-1
      子进程的死亡原因:
  • 正常死亡 WIFEXITED
    • 如果WIFEXITED为真,使用WEXITSTATUS得到退出状态
  • 非正常死亡 WIFSIGNALED
    • 如果 WIFSIGNALED为真,使用 WTERMSIG得到信号,知道s哈原因死了
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
        pid_t pid=fork();
        if(pid==0)  // 子进程的逻辑
        {
                printf("I am a child,I will die");

                sleep(2);
                return 101;
                //exit(102); 也行,头文件加<stdlib.h>

        }
        else if(pid>0)  //父进程的逻辑
        {
                printf("I am a parents,wait child to die\n");
                int status;  //  定义一个数字状态来记录子进程死亡的原因

                pid_t wpid=wait(&status);  //获取死亡原因,保存到status中
                printf("wait is ok,wpid is %d,pid is %d\n",wpid,pid);
                if(WIFEXITED(status))  // 正常死亡
                {
                        printf("child exit with %d\n",WEXITSTATUS(status));//显示退出那个101

                }
                if(WIFSIGNALED(status))  // 非正常死亡
                {
                        printf("child killed by %d\n",WTERMSIG(status));  // 寻找原因
                }
                while(1)  //在父进程的{}中,不让父进程死了
                {
                        sleep(1);
                }
        }
        return 0;

}

测试非正常死亡,只修改子进程这一点,其他不变,然后生成可执行文件,执行,然后再另外一个窗口用ps aux | grep a.out查看进程序号,然后kill -9 3545,把子进程给杀死,就会在原窗口看到非正常死亡的原因了。

int main()
{
        pid_t pid=fork();
        if(pid==0)
        {
                printf("I am a child,I will die");
                while(1)
                {
                        printf("guo lai da wo\n");
                        sleep(2);
                        //              return 101;
                        //exit(102);
                }
        }

父进程和子进程共享文件描述符表,读写控制位置也共享父进程子进程读写才没有覆盖,

10.

2、进程间通信ipc

1、ipc

Inter Process Communication:进程间通信,通过内核的缓冲区进行数据交换的机制
IPC通信方式:

  • pipe:管道,最简单,有血缘才
  • fifo:有名管道
  • mmap:文件映射共享IO,–速度最快,(把文件映射到内存,直接操作内存)
  • 本地socket 最稳定
  • 信号 携带信息量最小
  • 共享内存 :使用特定API申请的一块内存区域,大家都可以用
  • 消息队列

2.管道:

  • 从两个进程角度看:是半双工,
  • 从管道本身来看只能一边读一边写,所以有人说是:单工
    管道实现:通过内核创建一个伪文件:
    只能是有血缘关系才能通信,因为fork之后共享这两个文件描述符,其他进程根本找不到这两个文件描述符。
    01图片34444
    函数:
#include <unistd.h>
int pipe(int pipefd[2]);  //调用之后相当于在内核开辟了一个缓冲区
  • pipefd[2] 读写文件描述符,1:写 0:读
  • 返回值:0 成功 ,失败返回-1
    eg1、子进程写,父进程读:
#include<stdio.h>
#include<unistd.h>

int main()
{
        int fd[2];  //开辟一个数组,两个元素是文件描述符,一个代表读(0),一个代表写(1),
        pipe(fd);//pipe函数初始化,创建两个元素是文件描述符,调用pipe函数之后相当于在内核区开辟了一个缓冲区,一端写,一端读
        pid_t pid=fork();
        if(pid==0)
        {
                //son
                write(fd[1],"hello",5);
        }else if(pid>0)
        {
                char buf[12]={0};
                int ret=read(fd[0],buf,sizeof(buf));
                if(ret>0)
                        write(STDOUT_FILENO,buf,ret);
        }

        return 0;
}


eg2、实现ps -aux | grep bash命令
理解:标准输入,标准输出都在屏幕的左边

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{
        int fd[2];
        pipe(fd);  //调用此函数,就在内核缓冲区开辟了一片内存。
        pid_t pid=fork();
        if(pid==0)
        {
                //son
                //son ---> ps
                //1 先重定向,标准输出重定向到管道的写一端,标准输出的内容,直接写道管道中,输出到屏幕-->写入管道
                dup2(fd[1],STDOUT_FILENO);
                // 2 execlp 执行ps命令
                execlp("ps","ps","aux",NULL);
        }else if(pid>0)
        {
                //father
                //重定向,标准输入重定向到管道的读一端,将要在屏幕输入的内容直接定向到管道读的一端,输入的内容直接读出
                dup2(fd[0],STDIN_FILENO);
                execlp("grep","grep","bash",NULL);
        }
        return 0;
}
#问题:
# 管道的使用,虽然子进程把内容写给了父进程,但是父进程还掌握着管道的写端,所以由于管道的特性,父进程就会阻塞等待,而不会退出
解决办法:
把管道的使用细致化,父、子进程只掌握管道的一端
在子进程模块把读端关闭
close(fd[0]);
在父进程模块close(fd[1]);

读管道:

  • 写端全部关闭,—read 读到0,相当于读到文件末尾
  • 写端没有全部关闭
    • 有数据–read读到数据
    • 没有数据–read阻塞 fcntl 函数可以更改非阻塞
      写管道:
  • 读端全部关闭—产生一个信号SIGPIPE,程序异常终止
  • 读管道没有全部关闭
    • 管道已满:–write阻塞
    • 管道未满—write正常写入

命令ulimit -a,可以看到管道大小 521 * 8(而管道容量会更大一些,写满管道,能写的数据回会比这个大小更大)

  • 管道优点:
    • 简单
  • 缺点
    • 只能父子进程间通信,单方向,如果需要双方向通信,需要创建两根管道

3.fifo通信

fifo有名管道,实现无血缘关系进程通信

  • 创建一个管道的伪文件
    • 命令:mkfifo myfifo
    • 函数: int mkfifo(const char *pathname, mode_t mode);
  • 内核会针对fifo文件开辟一个缓冲区,操作fifo文件,可以操作缓冲区,实现建进程间通信,实际上就是文件读写

先mkfifo myfifo创建一个fifo文件,
myfifo_w.c写文件:

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<fcntl.h>

int main(int argc,char * argv[])
{
        if(argc!=2)
        {
                printf("./a.out fifomane\n");
                return -1;
        }
        //当前目录有一个myfifo文件,打开fifo文件
        int fd=open(argv[1],O_WRONLY);
        char buf[256];
        int num=1;
        while(1)  //循环写
        {
                memset(buf,0x00,sizeof(buf));
                sprintf(buf,"xiaoming%4d",num++);//先在缓冲区内写,
                write(fd,buf,strlen(buf));//再把缓冲区里边的写进fifo文件中
                sleep(1);
        }

        return 0;
}

myfifo_r.c 读文件;

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<fcntl.h>

int main(int argc,char * argv[])
{
        if(argc!=2)
        {
                printf("./a.out fifomane\n");
                return -1;
        }
        int fd=open(argv[1],O_RDONLY);

        char buf[256];
        int ret;
        while(1){
                ret=read(fd,buf,sizeof(buf));
                if(ret>0)
                {
                        printf("read:%s\n",buf);
                }
        }
        close(fd);
        return 0;
}

读出来fifo里边就没数据了
读端都关闭,写端自动关闭
fifo注意事项:读写两端都开才行

  • 打开fifo文件时,read端会阻塞等待write端打开,write端也会等待另外一端打开

4.mmap

函数原型

 #include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
 int munmap(void *addr, size_t length);

图34440001

创建映射区,

  • addr:传NULL
  • length:映射区长度
  • prot
    • PROT_READ:可读
    • PROT_WRITE:可写
  • flags
    • MAP_SHARED:共享的 (对内存修改会影响源文件)
    • MAP_PRIVATE:私有的
  • fd文件描述符,open打开一个文件
  • offset:偏移量(起始值)
  • 返回值
    • 成功 返回 可用的内存首地址
    • 失败 返回 MAP_FALED(-1)
int munmap(void *addr, size_t length);
  • addr:传mmap的返回值
  • length创建的长度
  • 返回值:成功返回0,失败-1
    eg.开启一个文件xxx.txt,并映射到内存中去,在内存中写入hello然后自动更新到文件中
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/mman.h>
#include<fcntl.h>
#include<string.h>

int main()
{
        int fd=open("xxx.txt",O_RDWR);
        //映射到内存中
        char * mem=mmap(NULL,8,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);

        if(mem==MAP_FAILED){
                perror("MAP err");
                return -1;
        }
        // close(fd);
        //内存中字符串拷贝
        strcpy(mem,"hello");

        munmap(mem,8);
        //release
        close(fd);
        return 0;
}

mmap问题
1.如果更改mem变量的地址,释放的时候munmap,传入的men还能成功吗?
不能。
2、如果对men越界操作会怎么样?
文件大小对映射区有影响,尽量避免。文件大,映射区开的小,写入内存中的字符串会正常映射过来,但是如果文件小,没有映射区大,则内存中的操作(字符串拷贝,就不能完全拷贝)会做不完。
3、如果文件偏移量随便填一个数会怎么样?
不行。offset必须是4K的整数倍(0,4096),一个文件的最小门槛时4096(8个512)
4、如果文件描述符先关闭,对mmap映射有没有影响?
没有影响。映射之后,通道已经通了。
5、open的时候,可以新创建一个文件来创建映射区吗?
不可以用大小为0的文件来映射,open一个新文件,需要给这个文件扩展非0容量之后就可以了。
6、open文件选择O_WRONLY,可以吗?
不可以,Permission denied 映射到内存操作,(隐藏一次读操作,先把文件读一下,再映射到内存)
7、当选择MAP_SHARED的时候,open文件选择O_RDONLY,prot可以选择PROT_READ|PROT_WRITE吗?
不可以。Permission denied 当为share时,open开的文件要多于等于内存要操作的权限。
8、mmap什么时候会报错?
上面很多情况都会报错。
9、不判断返回值会怎么样?
会死的很难看。

eg,实现父子进程通信
不过此处实现需要先指定一个文件,不好,后边给出匿名映射,

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<sys/wait.h>
#include<fcntl.h>
#include<sys/mman.h>

int main()
{
        int fd=open("xxx.txt",O_RDWR);
        int *mem=mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
        if(mem==MAP_FAILED)
        {
                perror("mmap err");
                return -1;
        }
        pid_t pid=fork();
        if(pid==0)
        {
                //子进程
                *mem=100;
                printf("child ,*mem=%d\n",*mem);
                sleep(4);
                printf("child ,*mem=%d\n",*mem);

        }else if(pid>0)
        {

                sleep(1);
                printf("parent ,*mem =%d\n",*mem);
                *mem=1001;
                sleep(1);
                printf("parent ,*mem =%d\n",*mem);
                sleep(4);//是为了等待儿子先死,或者直接使用后边一句,不然就会父进程先死,子进程最后那个输出会在shell进程之后输出的
                wait(NULL);

        }
        munmap(mem,4);
        close(fd);
        return 0;
}
#结果:
txdy827@ubuntu:~/test/ipc$ ./05_share_child 
child ,*mem=100
parent ,*mem =100
parent ,*mem =1001
child ,*mem=1001

匿名映射:
MAP_ANON
Synonym for MAP_ANONYMOUS. Deprecated.
MAP_ANONYMOUS

//      int fd=open("xxx.txt",O_RDWR);
        int *mem=mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);//第4个参数增加宏,d
//      close(fd);

5.

3、信号

1、简介:

信号的特点:简单,不能带大量信息,满足特定条件发生
信号的机制:进程B发送给进程A,内核产生信号,内核处理
信号的产生:

  • 按键产生 ctrl+c,ctrl+z,ctrl+\
  • 调用函数 kill raise abort
  • 定时器:alarm,setitimer
  • 命令产生kill
  • 硬件异常,段错误、浮点错误、总线错误、SIGPIPE
    信号的状态:
  • 产生
  • 递达:信号到达并处理完
  • 未决:信号被阻塞了
    信号的默认处理方式:
  • 忽略
  • 执行默认动作
  • 捕获
    信号4要素
  • 编号
  • 事件
  • 名称
  • 默认处理动作
    • 忽略
    • 终止
    • 终止+core
    • 暂停
    • 继续
      信号也叫软中断:软件产生的中断。有可能会有延迟
      9号19号信号不能捕获,不能忽略,不能阻塞
      阻塞信号集和未决信号集
      图555555555555555

终端按键产生信号
* ctrl+C 2号信号 终止中断 keyboard interrupt
* ctrl + Z 20号信号,暂停/停止
* ctrl+\ 3号信号sigquit 退出
硬件异常产生信号
* 除0,
* 非法访问内存—>段错误
* 总线错误

kill函数,命令产生信号
系统API
int kill(pid_t pid,int sig);
* pid>0:要发送进程ID
* pid==0代表当前调用进程组内所有进程
* pid=-1代表有权限发送的所有进程
* pid<0代表-pid对应的组内所有进程
sig—>对应的信号
eg1,3号子进程传递信号,杀死父进程

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

int main()
{
        int i;
        for(i=0;i<5;i++)  //循环创建子进程
        {
                pid_t pid=fork();
                if(pid==0)  //如果是子进程就推出去
                break;
        }
        if(i==2){  //3号子进程刺杀任务
                printf("I will kill father after 5 min\n");
                sleep(5);
                kill(getppid(),SIGKILL);
                while(1)
                {
                        sleep(1);
                }
        }else if(i==5)
        {
                while(1)
                {
                        printf("I am parent,very happy\n");
                        sleep(1);
                }
        }
        return 0;
}

eg2杀死3号子进程

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

int main()
{
        int i;
        int pid3;
        for(i=0;i<5;i++){
                pid_t pid=fork();
                if(pid==0){
                        break;
                }
                if(i==2){       //保存5号进程的ID
                        pid3=pid;
                }
        }
        //if(pid==0)  //不能用作判断
        if(i<5)
        {
                while(1)
                {
                        printf("I am child,my pid = %d,ppid is%d\n",getpid(),getppid());
                        sleep(5);
                }
        }
        //if(pid>0){
        if(i==5){
                sleep(1);
                printf("I will kill 3 pro after 5 s\n");
                kill(pid3,SIGKILL);
                while(1)
                {
                        sleep(1);
                }

        }
        return 0;
}

raise(sig);
abort();
进程自杀:

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

int main()
{
        printf("I will kill myself!\n");
        sleep(2);
        //raise(SIGKILL);
        abort();
        return 0;
}

2、时钟信号

alarm:

  • 定时给自己发送SIGALRM
  • 几秒后发送信号
  • 返回值,上次闹钟剩余的秒数
  • 特别的:如果传入秒数为0,代表取消闹钟
#include<stdio.h>
#include<unistd.h>

int main()
{
        int ret=0;
        ret=alarm(6);  // ret=6秒
        printf("ret is %d\n",ret);
        sleep(3);
        ret=alarm(5);  // ret =3秒
        printf("ret after is %d\n",ret);
        while(1)
        {
                printf("la da wo ya!\n");
                sleep(1);
        }
        return 0;
}
//代码解释:ret返回第一次剩余的秒数6-3==3,重新调用alarm相当于给他续了更多5秒信号发送的等待时间

3、setitimer

周期性的发送信号

//头文件:
 #include <sys/time.h>

//函数原型:
 int setitimer(int which, const struct itimerval *new_value,
                     struct itimerval *old_value);

 struct itimerval {
               struct timeval it_interval; /* 周期性的时间设置*/
               struct timeval it_value;    /* 下次的闹钟时间 */
           };

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

which
* ITIMER_REAL 自然定时法 SIGALRM
* ITIMER_VIRTUAL 计算进程的执行时间 SIGVTALRM
* ITIMER_PROF 进程的执行时间+调度时间 ITIMER_VIRTUAL
new_value 要设置的闹钟时间
*
old_value 原闹钟时间
可以当成alarm使用

#include<unistd.h>
#include<sys/time.h>
#include<stdio.h>

int main()
{
        struct itimerval myit={{0,0},{3,0}};
        setitimer(ITIMER_REAL,&myit,NULL);

        while(1){
                printf("who can kill me!\n");
                sleep(1);
        }
        return 0;
}

捕捉

  #include <signal.h>
typedef void (*sighandler_t)(int);  // 函数指针,函数void类型,参数int,定义了一个函数原型的声明
sighandler_t signal(int signum, sighandler_t handler);




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

void catch_sig(int num)  // 函数指针,和signal函数配套使用
{
        printf("cat %d sig \n",num);
}

int main()
{
        signal(SIGALRM,catch_sig);  //注册一下:如果出现了SIGNAL信号,就调用后边这个函数
        struct itimerval myit={{3,0},{5,0}};
        setitimer(ITIMER_REAL,&myit,NULL);

        while(1){
                printf("who can kill me!\n");
                sleep(1);
        }
        return 0;
}


4、信号集处理函数

清空信号集
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,失败是0
其他都是成功返回0,失败返回-1

设置阻塞或者解除阻塞信号集
int sigprocmask(int how,const sigset_t *set,sigset_t * oldset);

  • how
    • SIG_BLOCK:设置阻塞
    • SIG_UNBLOCK 解除阻塞
    • SIG_SETMASK:设置set为新的阻塞集信号
  • set传入的信号集
  • oldset旧的信号集,传出参数

获取未决信号集
int sigpending(sigset_t * set);

14 打印当前进程所有未决信号集

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<signal.h>
  4 
  5 int main()
  6 {
  7         sigset_t pend,sigproc;//声明两个信号集,第一个是要打印出来的,第二个是阻塞信号集
  8         sigemptyset(&sigproc);//设置阻塞信号,等待按键产生信号
  9         sigaddset(&sigproc,SIGINT);//把ctrl+c信号添加到这个信号集中,把这个信号添加之后,手动ctrl+c并不能终止进程
  	
  			sigaddset(&sigproc,SIGQUIT);//添加ctrl+\到阻塞信号集,手动ctrl+\也不能终止程序
  			sigaddset(&sigproc,SIGKILL);//添加-9kill杀死进程到阻塞信号集,不过这个信号即使添加到阻塞信号集了,这个信号也不能被阻塞。
 10 		//设置 阻塞 信号集
 11         sigprocmask(SIG_BLOCK,&sigproc,NULL);
 			循环获取未决信号集
 12         while(1){
 13 
 14                 sigpending(&pend);//获取所有信号
 15                 int i=0;
 16                 for(i=1;i<32;i++)
 17                 {
 18                         if(sigismember(&pend,i)==1){
 19 
 20                                 printf("1");
 21                         }
 22                         else{
 23                                 printf("0");
 24                         }
 25                 }
 26                 printf("\n");
 27         }
        return 0;
 29 }
#结果:
0110000000000000000000000000000 //ctrl+c是2号信号,被捕获到阻塞信号集,ctrl+\是3号信号,被捕获,
0110000000000000000000000000000//但是kill -9信号不能被捕获,再另一个窗口ps aux,找到进程然后杀死
已杀死

5、信号捕捉

防止进程意外死亡

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

* signum:要捕捉的信号
* handler:要执行的捕捉函数的指针,函数应该声明void fun(int)

另外一个函数

 #include <signal.h>

int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

signum:要捕捉的信号
act:传入的动作
oldact:原动作

 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);  //无效
           };


内核实现信号捕捉过程

6、借助SIGCHLD信号实现子进程回收

子进程运行结束,父进程会收到一个SIGCHLD的信号,默认忽略,

7、

4、守护进程

进程组:父进程

5、线程同步

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值