Linux之多进程

c程序获取进程pid和ppid

在 Linux 系统中管理进程使用树型管理方式每个进程都需要与其他某一个进程建立
父子关系, 对应的进程则叫做 父进程
Linux 系统会为每个进程分配 id , 这个 id 作为当前进程的唯一标识, 当进程结束, 则会回收
进程的 id 与 父进程的 id 分别通过 getpid() 与 getppid() 来获取

函数头文件

#include <sys/types.h>
#include <unistd.h>

函数原型

pid_t getpid(void);
pid_t getppid(void);

函数功能
获取当前进程 id 与 父进程 id
函数返回值
成功 : getpid() 返回当前进程 id ,getppid() 返回父进程 id
pid_t 的类型 实际为 int , 在系统中采用 typedef 的形式

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main(){

printf("pid=%d,ppid=%d",getpid(),getppid());
return 0;
}

进程相关命令

功能:显示当前进程的状态 (Process Status)
语法

ps [options]

常用语法选项
-A : 列出所有的进程
-e : 与 -A 功能类似
-w : 显示加宽可以显示较多的资讯
-au : 显示较详细的信息
-aux : 显示所有包含其他使用者的进程

在这里插入图片描述

示例 : ps -ef 列出所有的进程,相比 ps -aux 信息要少一些

在这里插入图片描述

示例 : ps -ef | grep “可执行文件名” 根据名称查找指定名字

在这里插入图片描述

top [-] [d delay] [q] [c] [S] [s] [i] [n] [b]

选项
d : 改变显示的更新速度,或是在交谈式指令列 (interactive command) 按 s
q : 没有任何延迟的显示速度,如果使用者是有 superuser 的权限,则 top 将会以最高的优先序执行
c : 切换显示模式,共有两种模式,一是只显示执行档的名称,另一种是显示完整的路径与名称
S : 累积模式,会将己完成或消失的子进程 (dead child process) 的 CPU time 累积起来
s : 安全模式,将交谈式指令取消, 避免潜在的危机
i : 不显示任何闲置 (idle) 或无用 (zombie) 的进程
n : 更新的次数,完成后将会退出 top
b : 批次档模式,搭配 “n” 参数一起使用,可以用来将 top 的结果输出到档案内

在这里插入图片描述

top -p <进程 id>

在这里插入图片描述

pstree 命令是将所有的进程以树型结构的方式进行展示

在这里插入图片描述

功能:kill 命令是用于结束进程的命令或者用于显示相关信号

kill -9  <pid号>

进程的创建

创建进程的函数需要调用 fork() 函数, 则会产生一个新的进程

函数头文件

#include <sys/types.h>
#include <unistd.h>

函数原型

pid_t fork(void);

函数功能:创建一个子进程
函数返回值
成功 : 返回给父进程是子进程 的 pid , 返回给子进程的是 0
失败 : 返回 -1, 并设置 errno

示例 : 创建一个子进程,并打印 HelloWorld

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

int main(){
pid_t pid;
pid=fork();
if(pid==-1){
perror("[ERROR fork():]");
return -1;
}
printf("hello\n");
return 0;
}

通过 fork() 函数创建子进程之后,有如下特点:

1、父子进程的执行顺序由操作系统算法决定的,不是由程序本身决定

2、子进程会拷贝父进程地址空间的内容, 包括缓冲区、文件描述符等

eg:

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

int main(){


write(1,"hello",5);
fputs("hello",stdout);

pid_t pid;
pid=fork();
if(pid==-1){
perror("[ERROR fork():]");
return -1;
}
return 0;
}

结果打印了三个hello

因为fputs是有缓冲区,fork子进程后会拷贝父进程地址空间的内容

多进程

在创建多个进程时, 最主要的原则为 由父进程统一创建,统一管理,
不能进行递归创建

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
int main(){

	pid_t pid;

	pid=fork();
	if(pid==-1){
	perror("[ERROR fork():]");
	exit(EXIT_FAILURE);
	}
	else if(pid==0){
	printf("process son A pid=%d\n",getpid());
	sleep(2);
	exit(EXIT_SUCCESS);
	}
	else if(pid>0){
		pid=fork();
		if(pid==-1){
	perror("[ERROR fork():]");
	exit(EXIT_FAILURE);

	}
	else if(pid==0){
	printf("process son B pid=%d\n",getpid());
	sleep(3);
	exit(EXIT_SUCCESS);
	}
	else if(pid>0){

	}
	}
return 0;
}

进程退出

exit()函数

函数头文件

#include <stdlib.h>

函数原型

void exit(int status);

函数功能:结束进程,并刷新缓冲区
函数参数
status : 退出状态值
在系统中定义了两个状态值 :
EXIT_SUCCESS: 正常退出
EXIT_FAILURE: 异常退出, 具体定义在 stdlib.h 中

#define EXIT_FAILURE 1 
#define EXIT_SUCCESS 

_exit函数

函数头文件

#include <unistd.h>

函数原型
void _exit(int status);
函数参数
status : 进程退出的状态值

函数功能:结束进程,不会刷新缓冲区

函数实例1 : exit.c

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

int main()
{
    printf("using exit----\n");
    printf("This is the content in buffer\n");
    exit(0);
}

执行结果为:

using exit----
This is the content in buffer

函数实例2:_exit.c

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

int main()
{
    printf("using _exit--\n");
    printf("This is the content in buffer");
    _exit(0);
}

执行结果为 :

using _exit-- 

printf函数就是使用缓冲I/O的方式,该函数在遇到“\n”换行符时自动的从缓冲区中将记录读出。所以exit()将缓冲区的数据写完后才退出,而_exit()函数直接退出

进程的等待

父进程 调用wait()与waitpid()函数等待子进程退出后,释放子进程遗留的资源

wait函数

函数头文件

#include <sys/types.h>
#include <sys/wait.h>

函数原型

pid_t wait(int *wstatus);

函数功能:让函数调用者进程进入到睡眠状态, 等待子进程进入僵死状态后,释放相关资源并返回

函数参数:

wstatus : 保存子进程退出状态值变量的指针,获取具体值需要使用WEXITSTATUS(status)宏定义

函数返回值:

成功 : 返回退出子进程的 pid
失败 :返回 -1

示例:创建一个子进程,延时3s后退出,父进程等待子进程后退出

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<sys/wait.h>
int main(){
pid_t pid;
pid=fork();

if(pid==-1){
perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
else if(pid==0){
	printf("the child process pid=%d\n",getpid());
sleep(3);
exit(66);
}
else if(pid>0){
int status=0;
pid_t cpid=wait(&status);
if(cpid==-1){
perror("[ERROR] wait():");
exit(EXIT_FAILURE);
}
printf("the child process pid=%d died status=%d\n",cpid,WEXITSTATUS(status));



}



return 0;
}

打印结果:

the child process pid=17596
the child process pid=17596 died status=66

在 wait 存储在 satus 变量的值, 存储了很多信息, 通过一系列 W 开头的宏来解析获取

WIFEXITED(status) : 进程是否正常结束

WEXITSTATUS(wstatus) : 获取进程退出状态值, exit 函数的参数

WIFSIGNALED(wstatus) : 表示该子进程是否被信号结束的, 返回真,则表示被信号结束的

WTERMSIG(wstatus) : 返回结束该子进程的那个信号的信号值

WCOREDUMP(wstatus) : 表示该子进程被信号唤醒的

WIFSTOPPED(wstatus) : 表示该子进程是否被信号中止 (stop) 的 , 返回真,则表示是被信号中止的

waitpid函数

函数头文件

#include <sys/types.h>
#include <sys/wait.h>

函数原型

pid_t waitpid(pid_t pid, int *wstatus, int options);

函数参数:
pid : 进程 id

​ -1 : 可以等待任意子进程
​ >0 : 等待 id 为 pid 的进程

wstatus : 保存子进程退出状态值变量的指针
options : 选项

​ 0:阻塞选项

​ WNOHANG: 非阻塞选项

函数返回值
成功 :
0 : 退出进程的 pid
= 0 : 在非阻塞模式下,没有进程退出
失败:
-1 并设置 errno

​ 示例 : 创建一个子进程, 子进程运行后 3s 退出, 父进程等待子进程退出

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<sys/wait.h>
int main(){
pid_t pid;
pid=fork();

if(pid==-1){

perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
else if(pid==0){
	printf("the child process pid=%d\n",getpid());
sleep(3);
exit(66);
}
else if(pid>0){
int status=0;
//使用阻塞方式等待进程结束=wait(status)
pid_t cpid=waitpid(-1,&status,0);

if(cpid==-1){
perror("[ERROR] wait():");
exit(EXIT_FAILURE);
}
printf("the child process pid=%d died status=%d\n",cpid,WEXITSTATUS(status));



}



return 0;
}

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<sys/wait.h>
int main(){
pid_t pid;
pid=fork();

if(pid==-1){

perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
else if(pid==0){
	printf("the child process pid=%d\n",getpid());
sleep(3);
exit(66);
}
else if(pid>0){
int status=0;
pid_t cpid;
//使用非阻塞方式,等待子进程结束后结束循环
while((cpid=waitpid(-1,&status,WNOHANG))==0){}

if(cpid==-1){
perror("[ERROR] wait():");
exit(EXIT_FAILURE);
}
printf("the child process pid=%d died status=%d\n",cpid,WEXITSTATUS(status));

}
return 0;
}

进程的替换

函数原型

int execl(const char *pathname, const char arg, … / (char *) NULL */);

int execlp(const char *file, const char
arg, … /(char *) NULL */);

int execle(const char *pathname, const char
arg, … /, (char *) NULL, char *const envp[] */);

int execv(const char *pathname, char *const argv[]);

int execvp(const char *file, char *const argv[]);

int execvpe(const char *file, char *const argv[], char *const envp[]);

函数参数
path:可执行文件的路径名
file : 可执行文件名,可以通过 path 环境变量指定的路径
arg : 参数列表,以 NULL 结尾
argv[] : 参数数组
envp[] : 环境变量数组

函数返回值:

  • 成功 : 0
  • 失败 : -1

示例 : 通过 execl 函数族执行 ls -l 命令

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(){

int result;
result=execl("/bin/ls","ls","-l",NULL);
if(result==-1){
perror("[ERROR] execl:");
}
return 0;
}

通过execv执行ls -l命令

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(){

int result;
char *argv[]={"ls","-l",NULL};//参数数组
result=execv("/bin/ls",argv);
if(result==-1){
perror("[ERROR] execl:");
}
return 0;
}

通过execvp执行ls -l命令

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(){

int result;
char *argv[]={"ls","-l",NULL};
result=execvp("ls",argv);//只需要指定 可执行文件名,但是只能通过path环境可以找到的
if(result==-1){
perror("[ERROR] execl:");
}
return 0;
}

进程间的通信

管道分为无名管道与有名管道

无名管道用于父子进程之间通讯
有名管道用于任意进程之间通讯

一、无名管道

无名管道的特点:

  • 无名管道属于单向通讯
  • 无名管道只能用于 父子进程通讯
  • 无名管道发送端叫做写端, 接收端叫做读端
  • 无名管道读端与写端抽象成两个文件进行操作,在无名管道创建成功之后,则会返回读端与写端的文件描述符

创建无名管道需要调用pipe()函数
函数头文件

#include <unistd.h>

函数原型

int pipe(int pipefd[2]);

函数参数
pipefd : 用于存储无名管道读端与写端的文件描述符的数组
pipefd[0] : 读端文件描述符
pipefd[1] : 写端文件描述符
函数返回值:
成功 : 0
失败 :-1, 设置 errno

代码示例:创建子进程,父进程通过管道向子进程发送 “Hello,pipe”

#include<stdio.h>
#include<unistd.h>
#include<wait.h>
#include<stdlib.h>
int main(){
	pid_t pid;
	int ret;
int pipefd[2];
ret=pipe(pipefd);//先创建管道,在fork进程
if(ret==-1){
perror("[ERROR] pipe():");
exit(EXIT_FAILURE);
}

pid=fork();
if(pid==-1){
perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
else if(pid==0){
ssize_t rbytes;
char buffer[64]={0};
close(pipefd[1]);//关闭写端
//当管道为空时,读管道会阻塞读进程
rbytes=read(pipefd[0],buffer,sizeof(buffer));

if(rbytes==-1){
perror("[ERROR] read():");
exit(EXIT_FAILURE);
}
printf("%s\n",buffer);
close(pipefd[0]);
}
else if(pid>0){
close(pipefd[0]);//关闭读端
ssize_t wbytes;
char buffer[64]={"hello pipe"};
wbytes=write(pipefd[1],buffer,sizeof(buffer));
if(wbytes==-1){
	perror("[ERROR] write():");
wait(NULL);//等待子进程结束
close(pipefd[1]);
exit(EXIT_FAILURE);
}
close(pipefd[1]);
wait(NULL);
}

return 0;
}

当管道为空时,读管道会阻塞读进程

当管道的写端被关闭了,从管道中读取剩余数据后,read 函数返回 0

在写入管道时,确保不超过 PIPE_BUF 字节的操作是原子的

当写入的数据达到 PIPE_BUF 字节时,write() 会在必要的时候阻塞直到管道中的可用空间足以原子地完成操作

当写入的数据大于 PIPE_BUF 字节时,write() 会尽可能多传输数据以充满这个管道

管道的大小是有限的, 不能让父/子进程同时对管道进行读/写操作

当一个进程试图想一个管道中写入数据但没有任何进程拥有该管道的打开着的读取描述符, 内核向写入进程发送一个 SIGPIPE 信号

二、有名管道

  • 有名管道是在文件系统中可见的文件, 但是不占用磁盘空间, 仍然在内存中, 可以通过 mkfifo命令创建有名管道
  • 有名管道与无名管道一样,在应用层是基于文件接口进行操作
  • 有名管道用于任意进程之间的通讯, 当管道为空时, 读进程会阻塞.

函数mkfifo()
函数头文件

#include <sys/types.h>
#include <sys/stat.h>

函数原型:

int mkfifo(const char *pathname, mode_t mode);

函数参数:
pathname : 有名管道路径名
mode : 有名管道文件访问权限

函数返回值:
成功 : 返回 0
失败 : 返回 -1, 并设置 errno
创建两个没有血缘关系的进程,使用有名管道进行进程间通讯

读程序

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#define FIFO_NAME "./fifo"
int main(){

int fd=open(FIFO_NAME,O_RDONLY);
ssize_t rbytes;
char buffer[64]={0};
if(fd==-1){
perror("[ERROR ]open():");
exit(EXIT_FAILURE);
}
rbytes=read(fd,buffer,sizeof(buffer));
if(rbytes==-1){

perror("[ERROR] read():");
exit(EXIT_FAILURE);
}

printf("%s\n",buffer);
close(fd);



return 0 ;
}

写程序

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

#define FIFO_NAME "./fifo"
int main(){
int fd;
ssize_t wbytes;
char buffer[64]={"hello fifo"};
mkfifo(FIFO_NAME,0644);
fd=open(FIFO_NAME,O_WRONLY);
if(fd==-1){
perror("[ERROR] open():");
exit(EXIT_FAILURE);
}
wbytes=write(fd,buffer,sizeof(buffer));
if(wbytes==-1){
perror("[ERROR] write:");
exit(EXIT_FAILURE);
}
close(fd);



return 0;
}

注意:
如果有名管道的一端以只读方式打开,它会阻塞到另一端以写的方式 (只写,读写)
如果有名管道的一端以只写方式打开,它会阻塞到另一端以读的方式 (只读,读写)

有名管道的优缺点:
优点:可以实现任意进程间通信(包括父子进程),操作起来和文件操作一样
缺点:

  1. 打开的时候需要读写一起进行否则就会阻塞,管道大小是 4096 个字节
  2. 半双工的工作模式,如果和多个进程通信则需要创建多个管道

三、信号

在 Linux 系统可以通过

kill -l

命令查看, 常用的信号列举如下:
SIGINT
该信号在用户键入 INTR 字符 (通常是 Ctrl-C) 时发出,终端驱动程序发送此信号并送到前台进>程中的每一个进程。
SIGQUIT
该信号和 SIGINT 类似,但由 QUIT 字符 (通常是 Ctrl-) 来控制。
SIGILL
该信号在一个进程企图执行一条非法指令时 (可执行文件本身出现错误,或者试图执行数据段、堆栈溢出时) 发出。
SIGFPE
该信号在发生致命的算术运算错误时发出。这里不仅包括浮点运算错误,还包括溢出及除数 > 为 0 等其它所有的算术的错误。
SIGKILL
该信号用来立即结束程序的运行,并且不能被阻塞、处理和忽略。
SIGALRM
该信号当一个定时器到时的时候发出。
SIGSTOP
该信号用于暂停一个进程,且不能被阻塞、处理或忽略。
SIGTSTP
该信号用于交互停止进程,用户可键入 SUSP 字符时 (通常是 Ctrl-Z) 发出这个信号。
SIGCHLD
子进程改变状态时,父进程会收到这个信号
SIGABRT
进程异常中止

当由进程来发送信号时, 则可以调用kill()函数与raise ()函数

kill函数

函数头文件

#include <sys/types.h>
#include <signal.h>

函数原型

int kill(pid_t pid, int sig);

函数功能:向指定的进程发送一个信号

函数参数
pid : 进程的 id
sig : 信号的 id

函数返回值:

成功: 返回 0
失败: 返回 -1, 并设置 errno

raise函数

函数头文件

#include <sys/types.h>
#include <signal.h>

函数原型

int raise(int sig);

函数参数:
sig : 信号编号

函数返回值
成功 : 返回 0
失败 : 返回 -1, 并设置 errno

示例:创建一个子进程,子进程通过信号暂停,父进程发送 终止信号

#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<wait.h>
int main(){
pid_t pid;
pid=fork();
if(pid==-1){
perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
else if(pid==0){
printf("child process  is running pid=%d\n",getpid());
raise(SIGSTOP);//给自己发送阻塞信号
printf("child process is stop pid=%d\n",getpid());
}
else if(pid>0){
	int ret;
sleep(1);
ret=kill(pid,SIGKILL);//给子进程发送终止信号

if(ret==0){
printf("parent pid=%d kill child pid=%d\n",getpid(),pid);
}
wait(NULL);
exit(EXIT_SUCCESS);

}
return 0;
}

pause() 函数

功能:pause() 函数会让调用它的进程挂起(暂停执行),直到接收到一个信号。这意味着程序会一直暂停,直到有外部事件(如用户输入或者其他进程发送的信号)唤醒它。它没有参数,也不允许指定暂停的时间长度。
头文件:

#include <unistd.h>

用法: pause(); // 程序暂停,等待信号唤醒。
返回值:接收到信号后,pause() 会返回接收到的信号的编号。如果没有信号到来,它就不会返回,也就是说正常情况下这个函数的调用不会返回到调用点

#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<wait.h>
int main(){
pid_t pid;
pid=fork();
if(pid==-1){
perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
else if(pid==0){
printf("child process  is running pid=%d\n",getpid());
pause();
printf("child process is stop pid=%d\n",getpid());
}
else if(pid>0){
	int ret;
sleep(3);
ret=kill(pid,SIGUSR1);//SIGUSR1也是终止信号

printf("parent pid=%d kill child pid=%d\n",getpid(),pid);
wait(NULL);
exit(EXIT_SUCCESS);

}
return 0;
}

自定义信号处理函数

对于每种信号都有相应的默认处理方式
进程退出:
SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM
进程忽略
SIGCHLD,SIGPWR,SIGURG,SIGWINCH
进程暂停
SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU

通过signal函数设置信号处理方式
函数头文件

#include <signal.h>

函数原型

sighandler_t signal(int signum, sighandler_t handler);

函数功能:
设置信号的处理方式, 如果是自定义处理方式,提供函数地址,注册到内核中
函数参数
signum : 信号编号

函数返回值
成功 : 返回信号处理函数地址
失败 : 返回 SIG_ERR , 并设置 errno

示例:创建一个子进程, 父进程给子进程发送 SIGUSR1 信号,并使用自定义的处理方式

#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<wait.h>
#include<string.h>
void do_signal_handler(int sig){

printf("Receive signal %s\n",strsignal(sig));
}
int main(){
pid_t pid;
pid=fork();
__sighandler_t h;
h=signal(SIGUSR1,do_signal_handler);
if(h==SIG_ERR){
perror("[ERROR] signal():");
exit(EXIT_FAILURE);
}

if(pid==-1){
perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
else if(pid==0){
printf("child process  is running pid=%d\n",getpid());
pause();
printf("child process is stop pid=%d\n",getpid());
}
else if(pid>0){
	int ret;
sleep(3);
ret=kill(pid,SIGUSR1);

printf("parent pid=%d kill child pid=%d\n",getpid(),pid);
wait(NULL);
exit(EXIT_SUCCESS);

}
return 0;
}

SIGALARM信号

函数头文件

#include <unistd.h>

函数原型

unsigned int alarm(unsigned int seconds);

函数功能:设置定时器的秒数
函数参数:
seconds : 定时的时间秒数
函数返回值:
返回上一次进程设置定时器剩余的秒数

要点:
定时器的定时任务由内核完成, alarm 函数值负责设置定时时间, 并告诉内核启动定时器
当定时时间超时后,内核会向进程发出 SIGALRM 信号

#include<stdio.h>
#include<string.h>
#include<unistd.h>
int main(){
int ret;
ret=alarm(5);
printf("ret=%d\n",ret);//返回上一次进程设置定时器剩余的秒数
sleep(2);
ret=alarm(4);
printf("ret=%d\n",ret);
return 0;

}

打印结果:

ret=0
ret=3
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
void do_handler_alarm(int sign){

printf("Receice <%s>\n",strsignal(sign));
}
int main(){
int ret;
ret=alarm(5);
__sighandler_t sigret;
sigret=signal(SIGALRM,do_handler_alarm);
pause();//等待计时器结束
return 0;

}

子进程退出信号SIGCHLD

问题:在使用 wait() 函数时,由于阻塞或者非阻塞都非常消耗资源,并且在阻塞情况下,父进程不能执行其他逻辑

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
#include<wait.h>
#include<stdlib.h>
void do_handler_chld(int sign){

printf("Receice <%s>\n",strsignal(sign));
wait(NULL);//不会阻塞,立即释放子进程的资源
}
int main(){
	pid_t pid;
__sighandler_t sigret;
sigret=signal(SIGCHLD,do_handler_chld);
pid=fork();
if(pid==-1){
perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}

else if(pid==0){
printf("chlid is running pid=%d\n",getpid());
sleep(2);
exit(EXIT_SUCCESS);
}
else if(pid>0){
while(1){}
}
	
	return 0;

}

默认处理方式是忽略的

四、消息队列

System V IPC 对象共有三种

  • 消息队列
  • 共享内存
  • 信号量

每个 IPC 对象都有一个唯一的 ID, 可以通过ftok()函数生成,ftok函数具体说明如下:

函数头文件

#include <sys/types.h>
#include <sys/ipc.h>

函数原型

key_t ftok(const char *pathname, int proj_id);

函数参数

  • pathname : 文件路径名
  • proj_id : 8 bit 的 id 整数

函数返回值:

成功: 返回合成的 key(key 由 文件的 inode 节点号 与 proj_id 构成,inode 节点号 :每个存在的文件操作系统都会有唯一的编号, 通过 ls - i 命令查看)
失败 : -1, 并设置 errno

ls - i 命令查看inode节点号

在这里插入图片描述

创建

函数头文件

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

函数原型

int msgget(key_t key, int msgflg);

函数参数
key : 由 ftok 函数合成
msgflg : 消息队列标志

  • IPC_CREAT : 创建标志
  • IPC_EXCL : 如果消息队列存在,则报错, errno 设置为 EEXIST
    权限控制标志

函数返回值
成功 : 返回 消息队列 id
失败 : 返回 -1,并设置 errno

#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<stdlib.h>
#include<string.h>
#define PATH "."
#define PRO_ID 88//8bit整数

int main(){
key_t key;
int mid;
key=ftok(PATH,PRO_ID);
if(key==-1){
perror("[ERROR] ftok():");
exit(EXIT_FAILURE);
}
mid=msgget(key,IPC_CREAT|0644);
if(mid==-1){
perror("[ERROR] msgget():");
exit(EXIT_FAILURE);
}
printf("message id=%d\n ",mid);


	return 0;
}


在这里插入图片描述

消息队列可以通过下面的命令查看

ipcs -q

删除消息队列

ipcrm -q [msqid]
删除

删除消息队列需要调用msgctl函数, 具体信息如下

函数头文件

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

函数原型

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

函数参数

msqid : 消息队列 id
cmd : 命令字

  • IPC_STAT:获取消息队列属性
  • IPC_SET : 设置消息队列属性
  • IPC_RMID : 删除消息队列属性 ,用此命名时,第三个参数为 NULL

buf : 消息队列属性结构体对象指针

消息队列属性结构体定义如下:

struct ipc_perm {
key_t __key; /* Key supplied to msgget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};

在上一个示例的基础上,加上删除队列的代码

#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<stdlib.h>
#include<string.h>
#define PATH "."
#define PRO_ID 88//8bit整数

int main(){
key_t key;
int mid;
int ret;
key=ftok(PATH,PRO_ID);
if(key==-1){
perror("[ERROR] ftok():");
exit(EXIT_FAILURE);
}
mid=msgget(key,IPC_CREAT|0644);
if(mid==-1){
perror("[ERROR] msgget():");
exit(EXIT_FAILURE);
}
printf("message id=%d\n ",mid);
ret=msgctl(mid,IPC_RMID,NULL);
if(ret==-1){
perror("[ERROR] msgctl():");
exit(EXIT_FAILURE);
}
	return 0;
}


发送

函数头文件

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

函数原型

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

函数参数:

1.msqid : 消息队列 ID
2.msgp : 消息结构体指针
3.msgsz : 消息内容的长度
4.msgflg : 消息队列标志,默认可以填 0
IPC_NOWAIT: 可以设置非阻塞

函数返回值
成功 : 返回 0
失败 : -1, 并设置 errno

消息结构定义形式如下:

struct msgbuf {//结构体名称自定义即可
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
接收

函数头文件

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

函数原型

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

函数参数:

msqid : 消息队列 id
msgp : 消息结构指针
msgsz : 消息内容的长度
msgtyp : 消息类型
msgflg : 消息队列标志,默认可以填 0
IPC_NOWAIT: 可以设置非阻塞

函数返回值:
成功 : 返回实际读取消息内容的字节数
失败 : -1, 并设置 errno

示例: 创建两个没有血缘关系的进程, 使用 消息队列进行通讯

write_message.c

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>

#define PATHNAME "."
#define PRO_ID 10

#define MSG_TYPE 100
#define MSG_SZ 64
struct msgbuf{
long mtype;
char mtext[MSG_SZ];
};

int main(){
key_t key;
key=ftok(PATHNAME,PRO_ID);
int msg_id=msgget(key,IPC_CREAT|0666);
if(msg_id==-1){
perror("[ERROR] msgget():");
exit(EXIT_FAILURE);
}
struct msgbuf message={MSG_TYPE,"hello msg queue"};
int ret=msgsnd(msg_id,&message,strlen(message.mtext),0);
if(ret==-1){
perror("[ERROR] msgsnd():");
exit(EXIT_FAILURE);
}
printf("message send success!");

return 0;
}

read_message.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<errno.h>
#define PATHNAME "."
#define PRO_ID 10

#define MSG_TYPE 100
#define MSG_SZ 64
struct msgbuf{
long mtype;
char mtext[MSG_SZ];
};

int main(){
struct msgbuf message;
ssize_t rbytes;
key_t key;
key=ftok(PATHNAME,PRO_ID);
int msqid=msgget(key,IPC_CREAT|0666);//接收方也需要mesget一次
if(msqid == -1){ perror("ftok(): "); exit(EXIT_FAILURE); }


msgrcv(msqid,&message,MSG_SZ,MSG_TYPE,0);
printf("Receice message %s\n types=%ld\n",message.mtext,message.mtype);
return 0;

}

五、共享内存

共享内存是将分配的物理空间直接映射到进程的用户虚拟地址空间中, 减少数据在内核空间缓存

共享内存是一种效率较高的进程间通讯的方式

在 Linux 系统中通过 ipcs -m 查看所有的共享内存

创建

函数头文件

#include <sys/ipc.h>
#include <sys/shm.h>

函数原型

int shmget(key_t key, size_t size, int shmflg);

函数功能
创建一个共享内存, 并返回 ID

函数参数
key : 由 ftok() 函数返回
size : 共享内存的大小
shmflg : 共享内存标志

  • IPC_CREAT : 创建标志
  • IPC_EXCL : 如果消息队列存在,则报错, errno 设置为 EEXIST
    权限控制标志

函数返回值

成功 : 返回 共享内存 id
失败 : 返回 -1, 并设置 errno

删除

函数头文件

#include <sys/ipc.h>
#include <sys/shm.h>

函数原型

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

函数功能

1.共享内存控制函数, 功能由具体的功能命令字决定
2.函数参数

  • shmid : 共享内存 id

  • cmd : 控制命令字

    ​ IPC_STAT: 获取 消息队列属性

    ​ IPC_SET : 设置消息队列属性

    ​ IPC_RMID : 删除消息队列属性 ,用此命名时,第三个参数为 NULL

3.buf : 共享内存属性结构体指针

消息队列属性结构体定义如下:

struct ipc_perm {
key_t __key; /* Key supplied to msgget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};

函数返回值

  • 成功 : 返回 0 , 特殊命令字除外
  • 失败 : 返回 -1

创建一个共享内存后,输出共享内存 id, 删除共享内存

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#define PATHNAME "."
#define PRO_ID 100
#define SZ 256
int main(){

key_t key;
key=ftok(PATHNAME,PRO_ID);
if(key==-1){
perror("[ERROR] ftok:");
exit(EXIT_FAILURE);
}
int shmid=shmget(key,SZ,IPC_CREAT|0666);
if(shmid==-1){
perror("[ERROR] shmid():");
exit(EXIT_FAILURE);
}
printf("shmid=%d\n",shmid);
int ret=shmctl(shmid,IPC_RMID,NULL);
if(ret==-1){
  perror("[ERROR] shmctl():");  
  exit(EXIT_FAILURE);
}


return 0;

}

映射

函数头文件

#include <sys/types.h>
#include <sys/shm.h>

函数原型:

void *shmat(int shmid, const void *shmaddr, int shmflg);

函数功能:将进程地址空间映射到共享内存上

函数参数:
shmid : 共享内存 id
shmaddr : 指定映射的到进程地址空间的起始地址,指定为 NULL 时, 由系统选择映射的地址
shmflg : 共享内存标志, 一般设置为 0

函数返回值:
成功 : 返回映射到进程地址空间的起始地址
失败 : (void *) -1, 并设置 errno

解除映射

函数头文件

#include <sys/types.h>
#include <sys/shm.h>

函数原型

int shmdt(const void *shmaddr);

函数功能:解除进程地址空间与共享内存的映射

函数参数:

shm_addr: 由shmat返回的地址指针

函数返回值:
成功 : 返回 0
失败 : 返回 -1, 并设置 errno

示例:使用共享内存进行进程间通讯

share_write.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>

#define PATHNAME "."
#define PRO_ID 101
#define SZ 256

int main(){
key_t key;
int shmid,ret;
void *addr=NULL;
key=ftok(PATHNAME,PRO_ID);
if(key==-1){
perror("[ERROR] ftok():");
exit(EXIT_FAILURE);
}

shmid=shmget(key,SZ,IPC_CREAT|0666);
if(shmid==-1){
perror("[ERROR] shmget():");
exit(EXIT_FAILURE);
}
printf("shmid=%d",shmid);
addr=shmat(shmid,NULL,0);
if(addr==(void *)-1){
perror("[ERROR] shmat():");
exit(EXIT_FAILURE);
}
memset(addr,'A',10);//给addr放10个A
shmdt(addr);
return 0;


}

share_read.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>

#define PATHNAME "."
#define PRO_ID 101
#define SZ 256

int main(){
key_t key;
int shmid,ret;
char buffer[10]={0};
void *addr=NULL;
key=ftok(PATHNAME,PRO_ID);
if(key==-1){
perror("[ERROR] ftok():");
exit(EXIT_FAILURE);
}

shmid=shmget(key,SZ,IPC_CREAT|0666);
if(shmid==-1){
perror("[ERROR] shmget():");
exit(EXIT_FAILURE);
}
addr=shmat(shmid,NULL,0);
if(addr==(void *)-1){
perror("[ERROR] shmat():");
exit(EXIT_FAILURE);
}
memcpy(buffer,addr,10);
printf("Receive %s\n",buffer);
shmdt(addr);
return 0;


}

进程间同步

信号量

互斥: 同一时刻只有一个进程访问临界资源
同步: 在互斥的基础上增加了进程对临界资源的访问顺序
进程主要的同步与互斥手段是信号量
信号量: 由内核维护的整数, 其值被限制为大于或等于 0

信号可以执行如下操作:
将信号量设置成一个具体的值
在信号量当前值的基础上加上一个数值
在信号量当前值的基础上减上一个数值
等待信号量的值为 0

一般信号量分为 二值信号量 与 计数信号量

  • 二值信号量: 一般指的是信号量 的值为 1, 可以理解为只对应一个资源
  • 计数信号量: 一般指的是值大于等于 2 , 可以理解为对应多个资源

在 Linux 系统中查询信号量使用

ipcs -s
创建信号量集合

函数头文件

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

函数原型

int semget(key_t key, int nsems, int semflg);

函数功能:创建一个信号量集合

函数参数
key : 由 ftok() 函数生成
nsems : 信号量的数量
semflg : 信号量集合的标志

  • IPC_CREAT : 创建标志
  • IPC_EXCL : 与 IPC_CREAT 标志一起使用, 如果信号量集合存在就报错
    权限标志

函数返回值
成功 : 返回信号量集合的 id
失败 : -1, 并设置 errno

设置信号量的数量

函数头文件

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

函数原型

int semctl(int semid, int semnum, int cmd, …);

函数功能:信号集合控制函数,根据 cmd 决定当前函数的功能

函数参数

  • semid : 信号量集合的 id
  • semnum : 信号量的编号,信号量的编号从 0 开始
  • cmd : 命令控制字
    SETVAL: 设置信号量的值
    GETVAL: 获取信号量的值
  • …: 后面是属于可变参参数列表, 根据不同的命令有不同的参数

信号量集合调用semctl函数,设置命令为IPC_RMID(删除信号量)时
注意 : 在使用 IPC_RMID 时,第 三个参数会被忽略

具体使用方法如下:

ret = semctl(semid,IPC_RMID,NULL);

函数返回值
成功 : 根据不同的命令有不同的返回值, 可以查看帮助文档关于 RETURN 的说明
GETNCNT the value of semncnt
GETPID the value of sempid
GETVAL the value of semval
GETZCNT the value of semzcnt.
All other cmd values return 0 on success.
失败 : 返回 -1, 并设置 errno

在使用命令时需要使用 union semun 共用体, 具体定义如下:

union semun {

int val; /* Value for SETVAL */
struct semid_ds   buf; /*Buffer for IPC_STAT, IPC_SET */

unsigned short array; /*Array for GETALL, SETALL */

struct seminfo__buf; /*Buffer for IPC_INFO
(Linux-specific) */

};

示例:创建一个信号量集合,集合中包含一个信号量, 并设置信号量的值为 1

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#define SEM_PATHNAME "."
#define SEM_PRO_ID 101
union semun{
int val;
};
int main(){
key_t key=ftok(SEM_PATHNAME,SEM_PRO_ID);
int sem_id,ret;
union semun s;
if(key==-1){
perror("[ERROR] ftok():");
exit(EXIT_FAILURE);
}
sem_id=semget(key,1,IPC_CREAT|0666);
if(sem_id==-1){
perror("[ERROR] semget():");
exit(EXIT_FAILURE);
}

s.val=1;
ret=semctl(sem_id,0,SETVAL,s);//初始化信号量的值,设置 第一个信号量的值为1
if(ret==-1){
perror("[ERROR] semctl():");
exit(EXIT_FAILURE);
}

return 0;
}

操作信号量

函数头文件

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

函数原型

int semop(int semid, struct sembuf *sops, size_t nsops);

函数功能:信号量操作函数,用于占用信号量、释放信号量、设置信号量等待
函数参数:

  • semid : 信号量集合 id
  • sops : 信号量操作结构体指针
  • nsops : 操作的信号量的数量

函数返回值:
成功 : 返回 0
失败 : 返回 -1, 并设置 errno

struct sembuf 结构体

struct sembuf{
unsigned short sem_num;//信号量编号, 从 0 开始
short sem_op;/*信号量操作
-1 : 占用资源
+1 : 释放资源
0 : 等待资源
*/
short sem_flg;
/*
信号量操作标志
IPC_NOWAIT : 非阻塞,在信号量的值为 0 时, 会立即返回
SEM_UNDO : 在进程终止时, 会自动释放信号量
*/
};

示例:使用信号量解决父子进程对终端的竞争

sem.h

#ifndef __SEM_H__
#define __SEM_H__

#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<stdlib.h>
//创建信号量 nsems:信号量的个数,对应信号量的值values
extern int sem_create(int nsems,unsigned short values[]);
//占用信号量 semid信号量集合的id ,对应要占用的信号量 eg:0 ,1,2
extern int sem_p(int semid,int semnum);
//释放信号量 semid信号量集合的id ,对应要占用的信号量 eg:0 ,1,2
extern int sem_v(int  semid,int semnum);
//删除信号量集合
extern int sem_del(int semid);


#endif

sem.c

#include "sem.h"
#define SEM_PATHNAME "."
#define SEM_PRO_ID 102
union semun{
unsigned short *array;
};

 int sem_create(int nsems,unsigned short values[]){
 key_t key;
 int sem_id,ret;
 union semun s;
 key=ftok(SEM_PATHNAME,SEM_PRO_ID);//生成key
 if(key==-1){
 perror("[ERROR] ftok():");
 exit(EXIT_FAILURE);
 }
 sem_id=semget(key,nsems,IPC_CREAT|0666);//创建信号量集合
 if(sem_id==-1){
 perror("[ERROR] semget():");
 exit(EXIT_FAILURE);
 }
 s.array=values;
 ret=semctl(sem_id,0,SETALL,s);//按位初始化(使用SETALL时,第二位参数设置0)
 if(ret==-1){
 perror("[ERROR] semctl():");
 exit(EXIT_FAILURE);
 }
 return sem_id;

 
 }
int sem_p(int semid,int semnum){
struct sembuf sops;
sops.sem_num=semnum;
sops.sem_op=-1;
sops.sem_flg=SEM_UNDO;
return semop(semid,&sops,1);
}
int sem_v(int  semid,int semnum){
struct sembuf sops;
sops.sem_num=semnum;
sops.sem_op=1;
sops.sem_flg=SEM_UNDO;
return semop(semid,&sops,1);
}

 int sem_del(int semid){
 return semctl(semid,0,IPC_RMID,NULL);

 }

main.c

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

#include"sem.h"
int main(){
pid_t cpid;
int semid;
unsigned short values[]={1};
semid=sem_create(1,values);
cpid=fork();
if(cpid==-1){
perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
else  if(cpid==0){
while(1){
sem_p(semid,0);
printf("----------------\n");
printf("C start\n");
sleep(1);
printf("C End \n");
printf("-----------------\n");
sem_v(semid,0);
}
}
else if(cpid>0){
sleep(1);
while(1){
sem_p(semid,0);
printf("----------------\n");
printf("P start\n");
sleep(1);
printf("P End \n");
printf("-----------------\n");
sem_v(semid,0);
}
}


return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/651709.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

马斯克:AI时代人人高收入,不需要工作,商品服务不再短缺,可能性80%

当前人工智能现状和未来如何&#xff1f;AI时代下&#xff0c;人类未来会发生哪些变化&#xff1f; 埃隆马斯克&#xff08;Elon Musk&#xff09;在2024 VivaTech大会上分享了关于地球未来的诸多愿景。 投资作业本课代表摘录了其中的要点&#xff0c;分享给大家&#xff1a…

c语言基础:数组的运用以及在内存中的地址的理解

目录 目录&#xff1a; 1.数组作为函数参数 2.数组在内存中的存储 2.1数组名是什么&#xff1f; 2.2下面我们来探讨二维数组的各个名字表示什么 二维数组的首元素地址是什么呢&#xff1f; *arr表示的是什么呢 &#xff1f;&#xff08;arr是二维数组&#xff09; 1.数组作…

C语言 | Leetcode C语言题解之第116题填充每个节点的下一个右侧节点指针

题目&#xff1a; 题解&#xff1a; struct Node* connect(struct Node* root) {if (root NULL) {return root;}// 从根节点开始struct Node* leftmost root;while (leftmost->left ! NULL) {// 遍历这一层节点组织成的链表&#xff0c;为下一层的节点更新 next 指针stru…

echarts学习篇

一、使用echarts 1.引入 Apache ECharts <!DOCTYPE html> <html> <head> <meta charset"utf-8" /> <!-- 引入刚刚下载的 ECharts 文件 --> <script src"echarts.js"></script> </head> </html> 2.…

设计模式 19 模板模式 Template Pattern

设计模式 19 模板模式 Template Pattern 1.定义 模板模式&#xff08;Template Pattern&#xff09;是一种行为设计模式&#xff0c;它定义了一个算法的骨架&#xff0c;将一些步骤的具体实现延迟到子类中。在模板模式中&#xff0c;定义了一个抽象类&#xff0c;其中包含了一个…

[论文笔记]Chain-of-Thought Prompting Elicits Reasoning in Large Language Models

引言 今天带来思维链论文 Chain-of-Thought Prompting Elicits Reasoning in Large Language Models的笔记。 作者探索了如何通过生成一系列中间推理步骤的思维链&#xff0c;显著提升大型语言模型在进行复杂推理时的能力。 1 总体介绍 语言模型的规模扩大已被证明能够带来…

基于Netty实现安全认证的WebSocket(wss)服务端

1.Netty服务端 服务端代码参考【基于Netty实现WebSocket服务端-CSDN博客】中的两种方式都可以&#xff1b;这里用的是第一种简单方式。 新增如下逻辑&#xff1a;添加SSLHandler SSLContext sslContext SslUtil.createSSLContext("JKS","D:\\workSpace\\day…

编译qt5.15.2(mac/windows)的mysql驱动(附带编译好的文件)

文章目录 0 背景1 编译过程2 福利 0 背景 因为需要连接到mysql数据库&#xff0c;所以需要连mysql驱动。 1 编译过程 1&#xff0c;打开文件/Users/mac/Qt5.14.2/5.14.2/Src/qtbase/src/plugins/sqldrivers/sqldrivers.pro&#xff0c;注释掉QMAKE_USE mysql&#xff1b; 如…

[数智人文实战] 02.舆情分析之词云可视化、文本聚类和LDA主题模型文本挖掘

【数智人文与文本挖掘】知识星球建立且正式运营,欢迎新老博友和朋友加入,一起分享更多数智人文知识和交流进步。该星球计划每周至少分享7个资源或文章,包括数智人文、文本挖掘、人工智能、大数据分析和图书情报的技术文章、代码及资源。同时,欢迎进入星球的朋友咨询我图情和…

多线程基本常识

多线程的状态 在Java中&#xff0c;一个线程的生命周期有以下几种状态&#xff1a; 新建&#xff08;New&#xff09;&#xff1a;当线程对象被创建时&#xff0c;线程处于新建状态。此时线程对象存在&#xff0c;但还没有调用start()方法启动线程。 运行&#xff08;Runnable…

Vulnhub - AI-WEB-1.0靶机教程

目录 站点信息收集 c段扫描 端口扫描 目录扫描 漏洞利用 使用 burp 抓包 查询数据库名 查询数据库下的表 查询表中的字段名 查询字段中的数据 --os-shell 上传一句话木马 下载地址&#xff1a;https://download.vulnhub.com/aiweb/AI-Web-1.0.7z 我们从站点信息收…

【C/C++】观察者模式

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

LAMP网络服务架构

目录 LAMP 网站服务架构 LAMP的组成部分 LAMP的构建顺序 安装论坛 0.电脑已编译安装Apache&#xff0c;MySQL&#xff0c;PHP 1.创建数据库&#xff0c;并进行授权 2.上传论坛压缩包到 /opt ,并解压 3.上传站点更新包 4.更改论坛目录的属主 5.浏览器访问验证 LAMP 网…

授权调用: 介绍 Transformers 智能体 2.0

简要概述 我们推出了 Transformers 智能体 2.0&#xff01; ⇒ &#x1f381; 在现有智能体类型的基础上&#xff0c;我们新增了两种能够 根据历史观察解决复杂任务的智能体。 ⇒ &#x1f4a1; 我们致力于让代码 清晰、模块化&#xff0c;并确保最终提示和工具等通用属性透明化…

PPT大珩助手新功能-生成迷宫

大珩助手是一款功能丰富的办公软件插件&#xff0c;它主要分为两个版本&#xff1a;PPT大珩助手和Word大珩助手。这两个版本都旨在提高用户在处理演示文稿和文档时的效率。 PPT大珩助手 这是一款专门为Microsoft PowerPoint设计的插件。它提供了多种功能&#xff0c;例如素材…

深入理解Kubernetes的调度核心思想

一、引言 Kubernetes&#xff08;简称K8s&#xff09;是一个开源的容器编排系统&#xff0c;用于自动化部署、扩展和管理容器化应用程序。在Kubernetes集群中&#xff0c;调度器是一个核心组件&#xff0c;它负责将Pod&#xff08;Kubernetes中的最小部署单元&#xff09;分配…

windows内存管理

一 windows系统的内存管理涉及哪些 1.1 虚拟内存管理机制 windows操作系统使用虚拟内存技术&#xff0c;将磁盘文件&#xff0c;通过映射对象&#xff08;存储在物理内存&#xff09;关联&#xff0c;映射到虚拟内存作为文件试图。即用户操作"虚拟内存中File View Objec…

【C语言】10.C语言指针(2)

文章目录 1.数组名的理解2.使用指针访问数组3.一维数组传参的本质4.冒泡排序算法步骤 5.二级指针6.指针数组7.指针数组模拟二维数组 1.数组名的理解 int arr[10] {1,2,3,4,5,6,7,8,9,10}; int *p &arr[0];这里我们使用 &arr[0] 的方式拿到了数组第一个元素的地址&am…

Stable Diffusion【写实模型】:逼真,逼真,超级逼真的国产超写实摄影大模型万享XL

今天和大家分享的是一个国产万享系列中使用量最高的大模型:万享XL_超写实摄影&#xff0c;顾名思义&#xff0c;该大模型主要是面向写实摄影&#xff0c;一方面生成的图片人物皮肤纹理细节超级逼真&#xff0c;另一方面对于光影效果的处理也非常到位。对于万享XL超写实摄影大模…

数组单调栈-901. 股票价格跨度、leetcode

单调栈作为一种数据结构在求解类递增、递减方面的题目中有较为广泛的应用&#xff0c;在以往的leetcode中所见到的相关单调栈的题目均为单一元素&#xff0c;今天刷到901题目时&#xff0c;想到了将数组元素作为单调栈中元素的方法进行求解。 题目链接及描述 901. 股票价格跨…