目录
linux进程相关
查看进程
aux
/*
USER 表示调用进程的用户 PID 表示进程ID %CPU表示cpu占用率 %MEM表示内存占用率 TTY表示在哪个终端启用 STAT表示进程状态 START 开始时间 TIME 持续时间 COMMAND执行该进程的命令
*/
ajx
/*
PPID 父进程ID PID当前进程ID PGID进程组的ID SID会话ID
*/
STAT参数意义
top
kill
创建进程
/*
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void); //创建子进程
返回值:返回值会返回两次 在父进程中返回子进程的PID 在子进程中返回0
可以通过fork返回值区分父进程和子进程
父进程中返回-1 表示子进程创建失败
*/
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main(){
int pid=fork();
if(pid>0){
printf("父亲 pid:%d ppid:%d\n",getpid(),getppid());
}else if ( pid ==0)
{
printf("儿子 pid:%d ppid:%d\n",getpid(),getppid());
}
for(int i=0;i<5;i++){
printf("pid %d %d\n",getpid(),i);
sleep(1);
}
return 0;
}
写时拷贝
传统的fork()系统调用直接把所有的资源复制给新创建的进程,这种实现过于简单并且效率低下。Linux的fork()使用写时拷贝(copy-on-write)页实现。写时拷贝是一种可以推迟甚至避免拷贝数据的技术。内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。只在需要写入的时候才会复制地址空间,从而使各个进程拥有各自的地址空间。也就是说,资源的复制是在需要写入的时候才会进行,在此之前,只有以只读方式共享。只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间
简单的理解为:内核只为新生成的子进程创建虚拟空间结构,它们来复制于父进程的虚拟究竟结构,但是不为这些段分配物理内存,它们共享父进程的物理空间,当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间。
文件共享
fork的一个特性是进程的所有打开文件描述符都被复制到子进程中,简单的理解为父子进程共享fork之前打开文件的读写漂移量。在 fork 之后处理文件描述符有以下两种常用的操作模式:
父进程等待子进程完成:在这种情况下,父进程无需对其描述符做任何处理。当子进程终止后,它曾进行过读、写操作的任一共享描述符的文件偏移量已做了相应更新。
父进程和子进程各自执行不同的程序段:在这种情况下,在 fork 之后,父进程和子进程各自关闭它们不需使用的文件描述符,这样就不会干扰对方使用的文件描述符。这种方法是网络服务进程经常使用的。
GDB进程调试
exec函数族
只会替换用户区的内容,不会修改内核区的数据
/*
#include <unistd.h>
extern char **environ;
int execl(const char *pathname, const char *arg, ...(char *) NULL );
pathname: 需要执行的文件的路径名称
arg: 执行该执行文件的参数(可变长度的参数)第一个参数一般没什么作用,一般写执行程序的名称,从第二个参数开始写需要的参数,参数最后需要以NULL结尾
返回值:只有出错时返回-1,成功无返回值
int execlp(const char *file, const char *arg, ...(char *) NULL );
file 需要执行的可执行文件的文件名(到环境变量中查找如果找不到就执行失败)
其余同上
int execle(const char *pathname, const char *arg, ..., (char *) NULL, char *const envp[] );
int execv(const char *pathname, char *const argv[]);
argv 需要的参数的字符串数组 例子: char *argv[]={"a","b",NULL}
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
envp 需要执行环境的字符串数组 例子: char *argv[]={"/home/hws","/home/lib",NULL}
*/
#include <unistd.h>
#include<stdio.h>
int main(){
int pid=fork();
if (pid>0)
{
printf("我是父亲 %d\n",getpid());
}else if (pid==0)
{
printf("我是孩子 %d\n",getpid());
execl("hello","hello",NULL);
}
printf("yes %d\n",getpid());
return 0;
}
进程控制
进程退出
exit是c语言库 _exit是linux库
/*
#include <unistd.h>
void _exit(int status);
#include <stdlib.h>
void exit(int status);
status 是进程退出时的状态信息,父进程回收子进程资源时可以获取
*/
#include <stdlib.h>
#include<stdio.h>
int main(){
printf("hello\n");
printf("world");
exit(0);//退出时会调用推出处理函数 刷新缓冲区
_Exit(0);//直接推出
return 0;
}
孤儿进程
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include <stdlib.h>
int main(){
int pid=fork();
int nums=10;
if(pid>0){
printf("父亲 pid:%d ppid:%d\n",getpid(),getppid());
exit(0);
}else if ( pid ==0)
{
sleep(10);
printf("儿子 pid:%d ppid:%d\n",getpid(),getppid());
}
for(int i=0;i<5;i++){
printf("pid %d %d\n",getpid(),i);
sleep(1);
}
return 0;
}
僵尸进程
想结束僵尸进程可以通过杀死其父进程打到目的。
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include <stdlib.h>
int main(){
int pid=fork();
int nums=10;
if(pid>0){
while (1)
{
/* code */
printf("父亲 pid:%d ppid:%d\n",getpid(),getppid());
sleep(1);
}
}else if ( pid ==0)
{
printf("儿子 pid:%d ppid:%d\n",getpid(),getppid());
}
// for(int i=0;i<5;i++){
// printf("pid %d %d\n",getpid(),i);
// sleep(1);
// }
return 0;
}
释放子进程
wait
/*
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);//等待任意一个子进程结束,如果任意一个子进程结束了,会回收子进程的内核空间 一次只能回收一个
wstatus 进程推出时的状态信息,传出参数
返回值 成功返回子进程的id 失败返回-1
调用wait函数的进程会被阻塞,如果没有子进程函数会立刻返回,返回-1
pid_t waitpid(pid_t pid, int *wstatus, int options);
*/
#include <sys/types.h>
#include <sys/wait.h>
#include<stdio.h>
#include<unistd.h>
int main(){
pid_t pid;
for (int i=0;i<5;i++){
pid=fork();
if(pid==0){
break;
}
}
if(pid>0){
while (1)
{
printf("父亲 %d\n",getpid());
int ret=wait(NULL);
printf("child die,pid %d",ret);
sleep(1);
}
}else{
while (1)
{
printf("孩子 %d %d\n",getpid(),getppid());
sleep(5);
}
}
return 0;
}
waitpid
/*
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);//等待任意一个子进程结束,如果任意一个子进程结束了,会回收子进程的内核空间 一次只能回收一个
wstatus 进程推出时的状态信息,传出参数
返回值 成功返回子进程的id 失败返回-1
调用wait函数的进程会被阻塞,如果没有子进程函数会立刻返回,返回-1
pid_t waitpid(pid_t pid, int *wstatus, int options);//回收指定进程号的子进程,可以设置是否为阻塞
pid:要回收的子进程id >0要回收的子进程的i号 =0回收当前进程组的所有子进程 =-1回收所有的子进程相当于wait() <-1回收某个进程组的id的绝对值
options 设置阻塞还是非阻塞 0表示阻塞WNOHANG非阻塞
返回值 >0返回子进程的id =0表示还有子进程活着 -1表示没有子进程了
*/
进程通讯
进程通信方式
匿名管道
/*
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
int pipe(int pipefd[2]);创建一个管道
pipefd:传出参数 返回管道两端的文件描述符 pipefd[0]对应管道的读端 pipefd[1]对应管道的写端
返回值: 成功返回0 失败返回-1
注意:匿名管道只能用于具有关系的进程之间的通信 ,管道是阻塞的,如果没有数据读就会阻塞 如果管道满了写数据就会阻塞
#include <unistd.h>
long fpathconf(int fd, int name);//获取管道的大小
int pipe2(int pipefd[2], int flags);创建一个管道
*/
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
int main(){
//fork之前创建管道
int pipefd[2];
int ret=pipe(pipefd);
if (ret==-1){
perror("pipe");
}
long size=fpathconf(pipefd[0],_PC_PIPE_BUF);
printf("pipe size :%ld",size);
int pid=fork();
if (pid>0){
printf("father pid:%d",getpid());
//父亲
char buf[1024];
while (1)
{
int len=read(pipefd[0],buf,sizeof(buf));
printf("child recive %s pid :%d\n",buf,getpid());
}
}else{
//孩子
printf("child pid:%d",getpid());
char *str="yes yes";
while (1)
{
/* code */
write(pipefd[1],str,sizeof(str));
sleep(1);
}
}
return 0;
}
父子进程通信案例
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
int main(){
//ps aux |grep xxx 子进程ps aux子进程结束后发送给父进程 父进程 获取到数据
int fd[2];
int ret=pipe(fd);
if (ret==-1){
perror("pipe");
exit(0);
}
int pid=fork();
if (pid>0){
//父亲
close(fd[1]);
char buf[1024]={0};
int len;
while ((len=read(fd[0],buf,sizeof(buf)-1))>0)
{
printf("%s",buf);
memset(buf,0,1024);
}
wait(NULL);
}else{
//关闭读
close(fd[0]);
dup2(fd[1],STDOUT_FILENO);
execlp("ps","ps","aux",NULL);
}
return 0;
}
设置管道非阻塞
/*
设置管道非阻塞
int flags = fcntl(fd[e],F_GETFL); // 获取原来的flagflags = 0 NONBLOCK;
// xiu
fcntl(fd[@],F_SETFL, flags);// 设置新的flag
*/
有名管道
有名管道的注意事项:
1.一个为只读而打开一个管道的进程会阻塞,直到另外一个进程为只写打开管道
2.一个为只写而打开一个管道的进程会阻塞,直到另外一个进程为只读打开管道
读管道:
管道中有数据,read返回实际读到的字节数管道中无数据:
管道写端被全部关闭,read返回9,(相当于读到文件末尾)写端没有全部被关闭,read阻塞等待
写管道:
管道读端被全部关闭,进行异常终止 (收到一个SIGPIPE信号 )管道读端没有全部关闭:
管道已经满了,write会阻塞管道没有满,write将数据写入,并返回实际写入的字节数。
/*
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
pathname 创建管道的名字和路径
mode 文件权限
返回值 成功返回0 失败返回-1
*/
read.c
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
int main(){
int fd=open("fifotest",O_RDONLY);
if (fd==-1){
perror("opne");
exit(0);
}
while (1)
{
char buf[1024]={0};
int len=read(fd,buf,sizeof(buf));
if(len==0){
printf("写端断开了");
break;
}
printf("shoudao buf %s",buf);
/* code */
}
close(fd);
return 0;
}
write.c
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
int main(){
int ret=access("fifotest",F_OK);
if(ret==-1){
printf("管道不存在创建管道\n");
ret=mkfifo("fifotest",0664);
if(ret==0){
perror("mkfifo");
}
}else{
printf("管道已存在\n");
}
//以只写的方式打开管道
int fd=open("fifotest",O_WRONLY);
if (fd==-1){
perror("opne");
exit(0);
}
for (int i=0;i<100;i++){
char buf[1024];
sprintf(buf,"this is %d\n",i);
printf("write %d\n",i);
write(fd,buf,sizeof(buf));
sleep(1);
}
close(fd);
return 0;
}
chat例子
A.c
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
int main(){
int ret=access("fifo1",F_OK);
if(ret==-1){
printf("管道不存在创建管道\n");
ret=mkfifo("fifo1",0664);
if(ret==0){
perror("mkfifo");
}
}else{
printf("管道已存在\n");
}
int fd1=open("fifo1",O_WRONLY);
if (fd1==-1){
perror("write");
exit(0);
}
int fd2=open("fifo2",O_RDONLY);
if (fd2==-1){
perror("read");
exit(0);
}
char buf[128]={0};
char buf1[128]={0};
while (1)
{ //先读
memset(buf,0,128);
int len=read(fd2,buf,sizeof(buf));
printf("收到 buf %s",buf);
//后写
memset(buf1,0,128);
fgets(buf1,128,stdin);
ret=write(fd1,buf1,strlen(buf1));
printf("写入 buf %s",buf1);
}
close(fd1);
close(fd2);
return 0;
}
B.c
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
int main(){
int ret=access("fifo1",F_OK);
if(ret==-1){
printf("管道不存在创建管道\n");
ret=mkfifo("fifo1",0664);
if(ret==0){
perror("mkfifo");
}
}else{
printf("管道已存在\n");
}
int fd1=open("fifo1",O_WRONLY);
if (fd1==-1){
perror("write");
exit(0);
}
int fd2=open("fifo2",O_RDONLY);
if (fd2==-1){
perror("read");
exit(0);
}
char buf[128]={0};
char buf1[128]={0};
while (1)
{ //先读
memset(buf,0,128);
int len=read(fd2,buf,sizeof(buf));
printf("收到 buf %s",buf);
//后写
memset(buf1,0,128);
fgets(buf1,128,stdin);
ret=write(fd1,buf1,strlen(buf1));
printf("写入 buf %s",buf1);
}
close(fd1);
close(fd2);
return 0;
}
内存映射
父子进程内存映射区通信
/*
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,//映射一个文件到内存
int fd, off_t offset);
addr:映射到内存中得起始地址 传递一个NULL 由内核指定
length:映射的内存的长度 使用文件的长度获取文件的长度 stat 或者lseek
prot 对申请的内存映射区的操作权限 PROT_EXEC PROT_READ PROT_WRITE PROT_NONE 要操作映射区必须有读取的权限
flag MAP_SHARED 映射区的数据会和磁盘文件同步 ,进程间通信必须使用该flag MAP_PRIVATE 不同步映射区的数据数据改变了不会修改原来的文件,会创建一个新的文件 MAP_SHARED_VALIDATE
fd 需要操作或者映射的文件描述符 文件的大小大于0不能为0
offset 偏移量 一般不用 必须指定4k的整数倍 一般填0
返回值:成功返回指向内存的地址 失败返回-1的地址
int munmap(void *addr, size_t length);//释放内存映射
addr 要释放的内存地址
length 释放内存的大小
使用内存映射实现进程之间的通信:
1.关系进程
先创建内存映射区,创建子进程,父子进程共享内存映射区
2.没有关系进程
准备一个大小非0的磁盘文件 进程一通过磁盘文件创建内存映射区 进程二通过磁盘文件创建内存映射区 使用内存映射区通信
内存映射区通信是非阻塞的
*/
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
#include <sys/mman.h>
int main(){
int fd=open("a.txt",O_RDWR);
int size=lseek(fd,0,SEEK_END);
//创建内存映射区
void *ptr =mmap(NULL,size,PROT_WRITE|PROT_READ,MAP_SHARED,fd,0);
if (ptr==MAP_FAILED){
perror("mmap");
exit(0);
}
//创建子进程
int pid=fork();
if(pid>0){
//父进程
wait();
char buf[64];
strcpy(buf,(char*)ptr);
printf("recive %s",buf);
}else{
//子进程
strcpy((char*)ptr,"yes yes yes ");
}
//关闭内存映射区
munmap(ptr,size);
close(fd);
return 0;
}
内存映射文件拷贝
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
#include <sys/mman.h>
int main(){
int fd=open("ff.txt",O_RDWR);
int size=lseek(fd,0,SEEK_END);
int newfd=open("copy.txt",O_RDWR|O_CREAT,0777);
if(newfd==-1){
perror("opennew");
exit(0);
}
//拓展文件大小
truncate("copy.txt",size);
//创建内存映射区
void *ptr =mmap(NULL,size,PROT_WRITE|PROT_READ,MAP_SHARED,fd,0);
void *ptr1 =mmap(NULL,size,PROT_WRITE|PROT_READ,MAP_SHARED,newfd,0);
if (ptr==MAP_FAILED){
perror("mmapold");
exit(0);
}
if (ptr==MAP_FAILED){
perror("mmapnew");
exit(0);
}
//内存拷贝
memcpy(ptr1,ptr,size);
munmap(ptr1,size);
munmap(ptr,size);
close(newfd);
close(fd);
return 0;
}
注意事项
1.如果对mmap的返回值(ptr)做++操作(ptr++),munmap是否能够成功?
void * ptr = mmap(…);
ptr++; 可以对其进行++操作munmap(ptr,len);// 错误,要保存地址
2.如果open时O RDONLY,mmap时prot参数指定PROT READPROT WRITE会怎样错误,返回MAP FAILED
open()函数中的权限建议和prot参数的权限保持一致。
3.如果文件偏移量为1999会怎样?偏移量必须是4k的整数倍,返回MAP FAILED
4.mmap什么情况下会调用失败?
第二个参数: length = 0
第三个参数: prot
只指定了写权限
prot PROT READ PROT WRITE第5个参数fd 通过open函数时指定的 O RDONLY / O WRONLY
5.可以open的时候O CREAT一个新文件来创建映射区吗?可以的,但是创建的文件的大小如果为@的话,肯定不行- 可以对新的文件进行扩展- lseek()truncate()
6.mmap后关闭文件描述符,对mmap映射有没有影响?int fd = open(“xxx”);mmap(,…,fd.close(fd);映射区还存在,创建映射区的fd被关闭,没有任何影响
7.对ptr越界操作会怎样?void * ptr = mmap(NULL, 100,.33.);越界操作操作的是非法的内存 ->段错误
匿名映射
不需要文件实体做内存映射 只适用于亲属关系的进程通信
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
#include <sys/mman.h>
int main(){
//创建匿名内存映射区
int size=4096;
void *ptr =mmap(NULL,size,PROT_WRITE|PROT_READ,MAP_SHARED|MAP_ANONYMOUS,-1,0);
if (ptr==MAP_FAILED){
perror("mmap");
exit(0);
}
int pid=fork();
if(pid>0){
printf("pid%d\n",getpid());
strcpy((char *)ptr,"hello");
wait(NULL);
}else{
sleep(1);
printf("%s pid%d\n",(char *)ptr,getpid());
}
//释放内存映射区
munmap(ptr,size);
return 0;
}
信号
信号函数
kill,raise,abort
/*
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);//给任何进程或者进程组pid 发送任何信号sig
pid >0将信号发送给指定进程 =0发送给当前进程组 =-1发送给每一个有权限接受信号的进程 <-1发送给进程组的id绝对值
sig 宏 等于0表示不发送信号
#include <signal.h>
int raise(int sig); //给当前进程发送信号
返回值 0代表成功 非0代表失败
#include <stdlib.h>
void abort(void); //发送SIGABRT给当前进程 杀死当前进程
*/
#include <fcntl.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
#include <sys/mman.h>
#include<signal.h>
int main(){
int pid=fork();
if(pid>0){
int i=0;
for (i=0;i<5;i++){
printf("%d\n",i);
sleep(1);
}
}else{
printf("父亲\n");
sleep(2);
printf("杀死\n");
kill(pid,SIGINT);
}
return 0;
}
alarm
/*
#include <unistd.h>
unsigned int alarm(unsigned int seconds);设置定时器当倒计时为0时,函数会给当前进程发送一个信号SIGALARM
seconds 倒计时市场 ,单位为秒
返回值 之前没有定时器返回0
之前有定时器,返回之前定时器的剩余时间
SIGALARM 进程收到这个信号,默认终止当前的进程,每个进程都有且只有一个唯一的定时器
该函数时不阻塞的 定时器与进程状态无关
*/
#include <fcntl.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
#include <sys/mman.h>
#include<signal.h>
int main(){
int seconds=alarm(5);
printf("seconds = %d\n",seconds);
sleep(2);
seconds=alarm(10);
printf("seconds = %d\n",seconds);
while (1)
{
/* code */
}
return 0;
}
setitimer
/*
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);//给任何进程或者进程组pid 发送任何信号sig
pid >0将信号发送给指定进程 =0发送给当前进程组 =-1发送给每一个有权限接受信号的进程 <-1发送给进程组的id绝对值
sig 宏 等于0表示不发送信号
/*
#include <sys/time.h>
int getitimer(int which, struct itimerval *curr_value);
int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value); //设置定时器 可以替代alarm 精度比alarm高 精度为微秒 可以实现周期性的定时器
which ITIMER_REAL真实时间包括内核时间用户时间和切换时间 时间到达后发送SIGALRM
ITIMER_VIRTUAL用户消耗的时间 时间到达后发送SIGVTALRM
ITIMER_PROF包括用户时间和内核时间 时间到达后发送SIGPROF
new_value 设置定时器的属性,
struct itimerval {定时器结构体
struct timeval it_interval; 每个阶段的时间 间隔时间
struct timeval it_value; 延迟多长时间执行
};
struct timeval {时间结构体
time_t tv_sec; 秒数
suseconds_t tv_usec; 微秒
};
过十秒后每隔两秒发送一次
old_value 记录上一次设置定时器的参数 不使用可以设置为NULL
返回值 成功返回0 失败返回-1
*/
#include <fcntl.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
#include <sys/mman.h>
#include<signal.h>
#include <sys/time.h>
int mian(){
//3妙后每两秒定时一次
struct itimerval new;
new.it_interval.tv_sec=3;
new.it_interval.tv_usec=0;
new.it_value.tv_sec=2;
new.it_value.tv_usec=0;
int res=setitimer(ITIMER_REAL,&new,NULL);
if (res==-1){
perror("settimer");
exit(0);
}
sleep(10);
return 0;
}
signal信号捕捉
/*
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);设置某个信号的捕捉行为
signum 要捕捉的信号
handler 捕捉到信号要如何处理
SIG_IGN 忽略信号
SIG_DFL 使用信号默认的行为
回调函数 函数由内核去调用,程序员只负责写,捕捉到信号后如何去处理
返回值 成功上一次注册的信号处理函数的地址,第一次调用返回NULL
失败返回SIG_ERR
SIGKILL SIGSTOP不能被捕捉 不能被忽略
*/
#include <fcntl.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
#include <sys/mman.h>
#include<signal.h>
#include <sys/time.h>
void myalarm(int nums){//int 类型的参数表示捕捉到的信号的值
printf("catch %d\n",nums);
printf("yes yes yes \n");
}
int main() {
//设置信号捕捉哈函数
signal(SIGALRM,myalarm);
// 过3秒以后,每隔2秒钟定时一次
struct itimerval new;
new.it_interval.tv_sec=3;
new.it_interval.tv_usec=0;
new.it_value.tv_sec=2;
new.it_value.tv_usec=0;
printf("开始了\n");
int res=setitimer(ITIMER_REAL,&new,NULL);
if (res==-1){
perror("settimer");
exit(0);
}
getchar();
return 0;
}
信号集
1.用户通过键盘 Ctrl + C, 产生2号信号SIGINT (信号被创建)
2.信号产生但是没有被处理 (未决)
- 在内核中将所有的没有被处理的信号存储在一个集合中 (未决信号集)
- SIGINT信号状态被存储在第二个标志位上
- 这个标志位的值为0, 说明信号不是未决状态
- 这个标志位的值为1, 说明信号处于未决状态
3.这个未决状态的信号,需要被处理,处理之前需要和另一个信号集(阻塞信号集),进行比较
- 阻塞信号集默认不阻塞任何的信号
- 如果想要阻塞某些信号需要用户调用系统的API
4.在处理的时候和阻塞信号集中的标志位进行查询,看是不是对该信号设置阻塞了
- 如果没有阻塞,这个信号就被处理
- 如果阻塞了,这个信号就继续处于未决状态,直到阻塞解除,这个信号就被处理
操作自定义信号集
/*
以下信号集都是对自定义的信号集进行操作
#include <signal.h>
int sigemptyset(sigset_t *set);//清空信号集中的数据 将信号集中所有的标志位置为0
set 传出参数 需要操作的信号集
返回值 成功返回0 失败返回-1
int sigfillset(sigset_t *set);将信号集中的所有标志 置为1
set 传出参数 需要操作的信号集
返回值 成功返回0 失败返回-1
int sigaddset(sigset_t *set, int signum);设置信号集中的某一个信号为1 表示阻塞这个信号
set 传出参数 需要操作的信号集
signum 需要阻塞的信号
返回值 成功返回0 失败返回-1
int sigdelset(sigset_t *set, int signum);设置信号集中的某一个信号为0 表示不阻塞这个信号
set 传出参数 需要操作的信号集
signum 需要不阻塞的信号
返回值 成功返回0 失败返回-1
int sigismember(const sigset_t *set, int signum);判断某个信号是否阻塞
set 传出参数 需要操作的信号集
signum 需要判断的信号
返回值 返回1表示阻塞 返回0表示不阻塞 返回-1表示出错
*/
#include <fcntl.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
#include <sys/mman.h>
#include<signal.h>
#include <sys/time.h>
int main(){
sigset_t set;
sigemptyset(&set);
int res=sigismember(&set,SIGINT);
if (res==0){
printf("不阻塞\n");
}else if(res==1){
printf("阻塞\n");
}else{
perror("sig");
}
res=sigaddset(&set,SIGINT);
res=sigismember(&set,SIGINT);
if (res==0){
printf("不阻塞\n");
}else if(res==1){
printf("阻塞\n");
}else{
perror("sig");
}
res=sigaddset(&set,SIGQUIT);
res=sigismember(&set,SIGQUIT);
if (res==0){
printf("不阻塞\n");
}else if(res==1){
printf("阻塞\n");
}else{
perror("sig");
}
res=sigdelset(&set,SIGINT);
res=sigismember(&set,SIGINT);
if (res==0){
printf("不阻塞\n");
}else if(res==1){
printf("阻塞\n");
}else{
perror("sig");
}
res=sigdelset(&set,SIGQUIT);`在这里插入代码片`
res=sigismember(&set,SIGQUIT);
if (res==0){
printf("不阻塞\n");
}else if(res==1){
printf("阻塞\n");
}else{
perror("sig");
}
return 0;
}
对内核阻塞集做处理
/*
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)//将自定义信号集中的数据设置到内核中 1.设置阻塞2.接触阻塞3.替换
how
SIG_BLOCK将用户设置的阻塞信号集添加到内核中非阻塞信号不做处理 mask|set
SIG_UNBLOCK将用户设置的非阻塞信号集添加到内核中阻塞信号不做处理 mask= mask&(~set)
SIG_SETMASK替换信号集
set 用户自定义的信号集
oldset 保存之前内核中的信号集 可以为NULL
返回值 成功返回0 失败返回-1
int sigpending (sigset_t *set)获取内核中的未决信号集
set 传出参数保存获取到的味觉信号集
int 成功返回0 失败返回-1
*/
#define _POSIX_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
#include <sys/mman.h>
#include<signal.h>
#include <sys/time.h>
#include <stdlib.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
int main(){
sigset_t set;
sigemptyset(&set);
sigaddset(&set,SIGINT);
sigaddset(&set,SIGQUIT);
sigprocmask(SIG_BLOCK,&set,NULL);
while (1)
{
sigset_t get;
sigemptyset(&get);
sigpending(&get);
for (int i=1;i<=31;i++){
if( (sigismember(&get,i))==0){
printf("0");
}else if( (sigismember(&get,i))==1){
printf("1");
}else{
perror("yes");
}
}
sleep(1);
printf("\n");
}
return 0;
}
sigaction信号捕捉
/*
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);检查或改变信号的处理 即信号的捕捉
signum 需要捕获的信号编号
act 捕捉到信号的处理动作
oldact 之前对信号捕捉的设置
返回值 成功返回0 失败返回-1
struct sigaction {
void (*sa_handler)(int);函数指针 捕捉到信号的处理
void (*sa_sigaction)(int, siginfo_t *, void *);不常用
sigset_t sa_mask;临时阻塞信号集,在信号捕捉执行函数执行中阻塞 结束后恢复
int sa_flags; 使用哪一个信号处理函数 如果0表示执行sa_handler 如果是SA_SIGINFO表示使用sa_sigaction
void (*sa_restorer)(void);废弃了 不需要使用
}
*/
#define _POSIX_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
#include <sys/mman.h>
#include<signal.h>
#include <sys/time.h>
void myalarm(int nums){//int 类型的参数表示捕捉到的信号的值
printf("catch %d\n",nums);
printf("yes yes yes \n");
}
int main() {
//设置信号捕捉哈函数
struct sigaction aa;
aa.sa_flags=0;
aa.sa_handler=myalarm;
sigemptyset(&aa.sa_mask);//清空临时阻塞信号集
sigaction(SIGALRM,&aa,NULL);
// 过3秒以后,每隔2秒钟定时一次
struct itimerval new;
new.it_interval.tv_sec=3;
new.it_interval.tv_usec=0;
new.it_value.tv_sec=2;
new.it_value.tv_usec=0;
printf("开始了\n");
int res=setitimer(ITIMER_REAL,&new,NULL);
if (res==-1){
perror("settimer");
exit(0);
}
while (1)
{
/* code */
}
return 0;
}
SIGCHLD信号
#define _POSIX_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
#include <sys/mman.h>
#include<signal.h>
#include <sys/time.h>
#include <sys/wait.h>
void myalarm(int nums){
while (1)
{
int ret=waitpid(-1,NULL,WNOHANG);
if (ret>0){
printf("杀死一个子进程%d\n",ret);
}else if (ret==0){
break;
}else{
break;
}
}
}
int main(){
int pid;
for(int i=0;i<20;i++){
pid=fork();
if (pid==0){
break;
}
}
if (pid>0){
struct sigaction aa;
aa.sa_flags=0;
aa.sa_handler=myalarm;
sigemptyset(&aa.sa_mask);//清空临时阻塞信号集
sigaction(SIGCHLD,&aa,NULL);
while (1)
{
printf("fatehr %d\n",getpid());
}
}else{
{
printf("son %d\n",getpid());
}
}
}
共享内存
/*
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);创建一个新的共享内存段,或者获取一个既有得共享内存段表示,新创建得内存段种得数据都为0
key整形 通过这个找到或创建一个共享内存 16进制非0
size 共享内存大小
shmflg 属性
访问权限 附加属性创建/判断共享内存是不是存在
创建 IPC_CREAT
判断是否存在IPC_EXCL需要和IPC_CREAT一起使用 IPC_CREAT|IPC_EXCL|0664
返回值:失败返回-1 成功大于0得值表示共享内存区的id
#include <sys/types.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);将内存区和当前进程关联
shmid 共享内存区的id由shmget返回值获取
shmaddr 申请共享内存的起始地址 可以输入NULL由内核指定
shmflg 对共享内存的操作 SHM_RDONLY必须有读权限才能操作内存 0读写权限
返回值:成功返回共享内存的首地址 失败(void *)-1
int shmdt(const void *shmaddr); 解除进程和共享内存的关联
shmaddr 共享内存的首地址
返回值 成功返回0 失败返回-1
int shmctl(int shmid, int cmd, struct shmid_ds *buf); 操作如删除共享内存,共享内存要删除才会消失,创建共享内存的进程被销毁对共享内存无影响
shmid 共享内存id
cmd 对共享内存的操作·IPC_STAT获取共享内存的当前状态 IPC_SET设置共享内存的状态 IPC_RMID标记共享内存被销毁
buf 需要设置或者获取的共享内存的属性信息
IPC_STAT buf存储数据
IPC_SET buf中存需要初始化的数据 设置到内核中
IPC_RMID 传NULL就可以
struct shmid_ds {
struct ipc_perm shm_perm; Ownership and permissions 权限
size_t shm_segsz; Size of segment (bytes) 大小
time_t shm_atime; Last attach time 上一次关联时间
time_t shm_dtime; Last detach time 上一次接触时间
time_t shm_ctime; Creation time/time of last 创建时间
modification via shmctl()
pid_t shm_cpid; PID of creator 创建进程的pid
pid_t shm_lpid; PID of last shmat(2)/shmdt(2)
shmatt_t shm_nattch; No. of current attaches 与共享内存关联的进程数量
};
key_t ftok(const char *pathname, int proj_id);根据指定的路径名和 int值生成共享内存的key
pathname 指定一个存在的路径
proj_id int类型 但是系统调用只会使用其中的一个字节范围0-255 一般指定一个字符'a'
返回值 成功返回生成的key 失败返回-1
*/
#include <fcntl.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
#include <sys/mman.h>
#include<signal.h>
#include <sys/time.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main(){
//创建一个共享内存区
int shmid=shmget(100,4096,IPC_CREAT|0664);
//和当前进程进行关联
void *ptr=shmat(shmid,NULL,0);
//写数据
char *str="hello world";
memcpy(ptr,str,strlen(str)+1);
printf("按任意键位继续");
getchar();
//接触关联
shmdt(ptr);
//删除共享内存
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
#include <fcntl.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
#include <sys/mman.h>
#include<signal.h>
#include <sys/time.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main(){
//创建一个共享内存区
int shmid=shmget(100,0,IPC_CREAT);
if (shmid==-1){
perror("shmget");
exit(0);
}
//和当前进程进行关联
void *ptr=shmat(shmid,NULL,0);
//读数据
printf("%s\n",(char*)ptr);
printf("按任意键位继续");
getchar();
//接触关联
shmdt(ptr);
//删除共享内存
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
问题1 操作系统如何知道一块贡献内存关联了多少进程?
共享内存维护了一个结构体struct shmid_ds 这个结构体中有一个成员shm_nattach记录了关联的进程个数
问题2 可以不可以对共享内存多次删除 shmctl
因为shmctl只是标记共享内存删除,只有当与之想关联的进程数为0时才真正删除。当共享内存的key为0时表示这个共享内存被标记删除。如果一个进程和共享内存取消关联,那么这个进程就不能继续操作这个共享内存。
问题3 共享内存和内存映射的区别
1.共享内存可以直接创建 内存映射需要磁盘文件
2.共享内存效率更高
3.共享内存 所有进程操作一快共享内存 内存映射每个进程在自己的虚拟空间中的内存时独立的
4.数据安全 进程突然推出:共享内存还存在 内存映射区消失
电脑死机 共享内存就没有了 内存映射区中的数据还存在
5.生命周期
进程退出 内存映射区销毁
进程退出 共享内存还在 如果一个进程退出会自动和一个共享内存区取消关联
守护进程
#define _POSIX_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
#include <sys/mman.h>
#include<signal.h>
#include <sys/time.h>
#include <fcntl.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
#include <sys/mman.h>
#include<signal.h>
#include <sys/time.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<time.h>
void myalarm(int nums){//int 类型的参数表示捕捉到的信号的值
int fd=open("/home/hws/tt/c2/l12/time.txt",O_RDWR|O_APPEND);
if(fd==-1){
perror("open");
}
time_t tm=time(NULL);
struct tm*loc=localtime(&tm);
char buf[1024];
sprintf(buf,"%d-%d-%d %d:%d:%d\n",loc->tm_year,loc->tm_mon,loc->tm_mday,loc->tm_hour,loc->tm_min,loc->tm_sec);
printf("%s\n",buf);
write(fd,buf,strlen(buf));
close(fd);
return ;
}
int main(){
/*
守护进程每隔两秒获取系统时间,将时间写入磁盘文件
*/
int pid=fork();
if(pid>0){
exit(0);
}else{
int sid=setsid();
int res=umask(022);
if (res==-1){
perror("umask");
}
res=chdir("/");
if (res==-1){
perror("chdir");
}
// 关闭文件描述符
int fd=open("/dev/null",O_RDWR);
dup2(fd,0);
dup2(fd,1);
dup2(fd,2);
// 6.业务逻辑
// 捕捉定时信号
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = myalarm;
sigemptyset(&act.sa_mask);
sigaction(SIGALRM, &act, NULL);
struct itimerval val;
val.it_value.tv_sec = 2;
val.it_value.tv_usec = 0;
val.it_interval.tv_sec = 2;
val.it_interval.tv_usec = 0;
// 创建定时器
setitimer(ITIMER_REAL, &val, NULL);
printf("到这里");
while (1)
{
sleep(10);
}
}
return 0;
}