首页 > 系统相关 >linux之信号操作(九千字长文详解)

linux之信号操作(九千字长文详解)

时间:2024-01-11 12:06:36浏览次数:36  
标签:九千 int 进程 屏蔽 handler 信号 linux 字长 include

linux之信号操作

sigset_t

这是信号在内核中的表示

image-20230804111055508

==block和pending都是位图——即用bit位来表示信号编号!==

每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。

因此,未决和阻塞标志可以用==相同的数据类型sigset_t==来存储,==sigset_t称为信号集==,这个类型可以表示每个信号 的“有效”或“无效”状态(在软件层面上就是将bit为从0置为1,或者从1置为0,硬件上就是从充放电来表示)

在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有 效”和“无效”的含义是该信号是否处于未决状态。

同一种位图我们可以用不同的解释来改变它的含义

==阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),==这里的“屏蔽”应该理解为阻塞而不是忽略。

信号集操作函数

sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做任何解释(如用printf直接打印sigset_t变量是没有意义的)

虽然本质都是位图,但是每个操作系统的实现方式是不一样的!不要自己使用逻辑与或非去进行判断,操作

#include <signal.h>
int sigemptyset (sigset_t *set);
int sigfillset (sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset (sigset_t *set, int signo);
int sigismember (const sigset_t *set, int signo); 

==参数set就是——信号集,参数signo——就是那个信号(那个比特位)==

函数sigemptyset——初始化set所指向的信号集,使其中所有信号的对应bit都置为0。表示该信号集不包含任何有效信号。

函数sigfillset——初始化set所指向的信号集,使其中所有信号的对应bit置为1,表示该信号集的有效信号包括系统支持的所有信号。

注意,在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号

函数sigaddset——将一个特定的信号添加进这个信号集里面!

函数sigdelset——将特定的信号从信号集里面删除

函数sigismember——判断该信号存不存在信号集里面

前4个函数都是成功返回0,出错返回-1。

==sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含 某种 信号,若包含则返回1,不包含则返回0,出错返回-1。==

sigprocmask

==这个函数的作用就是用来更该进程的block表!==——那个进程调用就修改那个进程!

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 

返回值:若成功则为0,若出错则为-1

==how参数==——表示如何修改这个block表,有三个不同的选项,SIG_BLOCK,SIG_UNBLOCK,SIG_SETMASK

BIG_BLOCK set包含了我们希望添加到当前信号屏蔽字的信号(新增信号屏蔽字),相当于mask=mask|set
BIG_UNBLOCK st包含了我们希望从当前信号屏蔽字中解除阻塞的信号(删除信号屏蔽字),相当于mask=mask&~set
SIG_SETMASK 设置当前信号屏蔽字为set所指向的值(重置当前的信号屏蔽字重置为set),相当于mask=set

如何理解三个选项呢?——就像有个老师布置作业,说布置800字的作文

老师在在800字作文的基础上,继续增加作业,例如多三道数学题——这就是SIG_BLOCK(增加)

老师将800字作文和三道数学题,变成800字作文和一道数学题——这既是BIG_UNBLOCK(减少)

老师收到通知将800字作文和三道数学题,变成了一个物理实验报告!重新布置作业,其他就不用做了——这就是SIG_SETMASK(即重置)

==set参数==——这个参数和how参数强相关!

假如我们想要对该进程的所有信号进程屏蔽!

那么我们就可以传入一个全1的set信号集!——使用sigfilset函数设置!

然后选择SIG_SETMASK操作(重置操作),就会将我们传进的set的位图结构,设置进进程的block位图里面!

==oset参数——是个输出参数!==

如果我们想要对信号屏蔽字做恢复呢?——所以我们就得将老的信号屏蔽字保存起来!

表示,当我们对信号屏蔽字做修改的时候,老的信号屏蔽字就会被返回!

sigpengding

 #include <signal.h>
int sigpending(sigset_t *set);

读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1。

我们接下来可与用上面的函数做一些实验!

我们知道,一般情况:我们所有的信号都是不被阻塞的!如果一个信号被阻塞,那么该信号就不会被抵达!如果收到了为阻塞的信号,那么就会被存入pending位图里面!

#include<iostream>
#include <signal.h>
#include<string>
#include<vector>
#include<unistd.h>
// #define BLOCK_SIGNAL 2
#define MAX_SIGNUM 31

static std::vector<int> sigv = {2};//想要屏蔽那些信号往这个数组加就可以了!
static void show_pending(const sigset_t& pending)
{
       std::string show;
       for(int i = MAX_SIGNUM;i>=1;i--)
       {
           if(sigismember(&pending,i) == 1)
               show +='1';
           else 
               show +='0';
       }
       std::cout << show << std::endl;
}
int main()
{
       //首先尝试屏蔽2号信号!
       sigset_t set,oset,pending;
       //1.1初始化!
       sigemptyset(&set);//全部设为0
       sigemptyset(&oset);
       sigemptyset(&pending);
       //1.2添加要屏蔽的信号
       for(auto e:sigv)
       {
           sigaddset(&set, e); // 在set信号集将e号信号色设置为1
       }
       //前面这一堆动作是没有影响该进程信号屏蔽字的!——只是在用户层构建一个信号集!
       //1.3屏蔽信号!
       sigprocmask(SIG_BLOCK,&set,&oset);//将信号集设置进内核!

       //遍历打印所有的pending信号集!
       while(true)
       {
           //2.1获取
           sigpending(&pending);//获取该进程的pending表
           //2.2打印pending表!
           show_pending(pending);
           sleep(1);
       }
       return 0;
}

image-20230804165111603

==我们发现当我们使用ctrl+c发送2号信号后,2号信号就被阻塞!然后pending表里面的2更好信号位置就变了1!==

9号信号是绝不能被屏蔽,绝不能被捕抓!

==如果我们想要接触信号屏蔽!==

#include<iostream>
#include <signal.h>
#include<string>
#include<vector>
#include<unistd.h>
#define MAX_SIGNUM 31

static std::vector<int> sigv = {2};
static void show_pending(const sigset_t& pending)
{
    std::string show;
    for(int i = MAX_SIGNUM;i>=1;i--)
    {
        if(sigismember(&pending,i) == 1)
            show +='1';
        else 
            show +='0';
    }
    std::cout << show << std::endl;
}
int main()
{
    sigset_t set,oset,pending;

    sigemptyset(&set);//全部设为0
    sigemptyset(&oset);
    sigemptyset(&pending);

    for(auto sig:sigv)
    {
        sigaddset(&set, sig);
    }

    sigprocmask(SIG_BLOCK,&set,&oset);

    int cnt = 5;
    while(true)
    {
        //2.1获取
        sigpending(&pending);//获取该进程的pending表
        //2.2打印pending表!
        show_pending(pending);
        sleep(1);
        //cnt秒之后接触屏蔽!
        if(cnt-- == 0)
        {
            std::cout << "Restore all signals" << std::endl;
              
            sigprocmask(SIG_SETMASK,&oset,&set);
            //sigprocmask(SIG_UNBLOCK,&set,&oset);//这样子也可以!
            
            //这里无法打印!
            std::cout << "Restore all signals" << std::endl;
        }
    }
    return 0;
}

image-20230804171630098

我们会发现5s之后信号确实被解除了阻塞,直接让进程被终止了!但是为什么没有打印两个语句出来呢??

==一旦对特定信号接触屏蔽,那么操作系统至少要抵达一个信号!==

那么从内核态返回用户态之后就把进程终止了,压根不会回到进程的里面继续执行后续代码,所以无法打印!!!

==如果想要信号在被接触阻塞之后能打印!——我们可以对信号进行捕抓!==

#include<iostream>
#include <signal.h>
#include<string>
#include<vector>
#include<unistd.h>
#define MAX_SIGNUM 31

static void myhandler(int signo)
{
    std::cout << "The signal has been arrested" << std::endl;
    std::cout << "Restore all signals" << std::endl;
}
int main()
{
    for(auto sig:sigv)//信号捕抓!
    {
        signal(sig,myhandler);
    }
    //....
    return 0;
}

image-20230804172352012

sigaction

#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

和signal一样这也是一个信号捕抓函数!功能也是一样的!即对特定信号设定特定的回调方法!——但是sigaction有更详细的选项设置

==首先我们就要认识一下struct sigaction这个结构体==

//The sigaction structure is defined as something like:
struct sigaction {
    void     (*sa_handler)(int);//设置普通信号的回调函数
    void     (*sa_sigaction)(int, siginfo_t *, void *);//设置实时信号的回调函数
    sigset_t   sa_mask;
    int        sa_flags;
    void     (*sa_restorer)(void);
};

这个函数可以用来处理实时信号和普通信号!——不过我们这里只关心普通信号!

==我们主要设置该结构体的sa_handler这个成员变量!,sa_sigaction一般设置为nullptr,sa_flags也设置为0即可,sa_restorer也是和实时信号相关(一般也不使用)——设置为nullptr即可==(一般sa_handler和sasa_sigaction是最好不要同时同时使用!)

==我们后面主要讨论的是sa_mask这个成员变量!==

参数signum——就是信号编号!

参数act——是一个输入信参数,对我们当前进程的信号捕抓逻辑进行影响或者修改!

信号oldact——是一个输出型参数!获取特定信号老的处理方法!

返回值——成功返回0,不成功返回-1

==我们可以试用一下==

#include<signal.h>
#include<iostream>
#include<unistd.h>
using namespace std;

void handler(int signo)
{
    cout << "get a signo :" << signo << endl;
}
int main()
{
    struct sigaction act,oact;
    act.sa_flags = 0;
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    sigaction(SIGINT,&act,&oact);

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

image-20230805112443192

==我们可以看到和我们使用signal函数其实是一样的!那么sigaction函数和signal函数的区别究竟是什么?==

#include<signal.h>
#include<iostream>
#include<unistd.h>
#include<cstdio>
using namespace std;
void Count(int cnt)
{
    while(cnt)
    {
        printf("cnt: %d\r",cnt);
        fflush(stdout);
        cnt--;
        sleep(1);
    }
    printf("\n") ;
}
void handler(int signo)
{
    cout << "get a signo :" << signo << "is running"<< endl;
    Count(20);//我们使用Count函数来模拟handler处理时间长的场景!
}
int main()
{
    struct sigaction act,oact;
    act.sa_flags = 0;
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    sigaction(SIGINT,&act,&oact);

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

**当handler函数==比较废时间的时==候,如果多次收到相同的信号会发生什么呢?**会不会在handler函数的内部递归式的调用handler呢?——也就是说进程在运行的时候可能会收到大量的同类型的信号!如果收到同类型的信号,当前又正在处理某一个信号时,会发生什么呢?操作系统会不会允许我们进行那么多的信号提交呢?

image-20230805114647905

==我们可以发现在第一次收到信号信后,在信号处理期间,信号都不会再抵达!后面无论是收到了多少个信号都只会处理一个信号!==

当我们正在抵达某一个信号的期间!同类型信号无法被抵达!——因为当前信号被捕抓,==系统会自动将当前信号加入到信号的信号屏蔽字(block表里面!)==!而当信号完成捕抓动作!系统又会==自动解除该信号的屏蔽!==

如果我们该handler里面尝试的屏蔽正在抵达的信号!例如:2号信号!

==那么就会出现虽然我们屏蔽了!——但是屏蔽完过后又被操作系统给自动的解除了!==

还能捕抓一次是因为,在首次收到信号的时候,pending位图会被由1置0,然后后续收到信号的时候,就会再次将0置为1!——但是因为只有一个位图所以只能改一次!

==一般一个信号被解除屏蔽的时候,会自动进行抵达!(至少要抵达一次!)如果这个信号已经在pending表里面了!没有就不进行任何动作!==

==处理信号信号的原则是串行的处理同类信号!不允许递归的进行处理!(只有处理完一个,才能处理下一个!)==

上面我们说过,信号处理的时候是不能去屏蔽同信号(因为会被自动的接触!)那么如果我们想要在处理本信号的时候,顺便屏蔽其他类型的信号呢?——可以的,我们可以添加进==sa_mask==里面,这样就是这个成员变量的作用!

#include<signal.h>
#include<iostream>
#include<unistd.h>
#include<cstdio>
using namespace std;
void Count(int cnt)
{
    while(cnt)
    {
        printf("cnt:%d",cnt);
        fflush(stdout);
        cnt--;
        sleep(1);
    }
    printf("\n");
}
void handler(int signo)
{
    cout << "get a signo :" << signo << "is running"<< endl;
    Count(20);
}
int main()
{
    struct sigaction act,oact;
    act.sa_flags = 0;
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);    
    //我们直接向
    sigaddset(&act.sa_mask,3);//将三号信号设置进mask里面!
    //这样子在除了屏蔽2号信号本身,还会将3号信号给屏蔽!
    sigaction(SIGINT,&act,&oact);

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

image-20230807112442518

在处理2号信号的时候,2,3号信号就都被屏蔽了!

image-20230807112544560

==只有处理完2号信号,操作系统才会将2号信号和3号信号都解除屏蔽!——此时3号信号就可以抵达了!这样子进程就被终止了!==

总结

当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来 的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么 它会被阻塞到当前处理结束为止。

如果 在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字

可重入函数

假设有一个链表,我们要对其进行头插!

image-20230807161922843

正常看起来是这样没错!

==但是假如出现了一些情况!——插入的第一步的时候因为某些原因触发了信号!而信号handler是我们的自定义行为,里面一个也有插入会发生什么呢?==

image-20230807163435618

==我们发现,虽然都执行成功了!——但是插入顺序却出现问题了!导致了我们内存节点丢失!==

之所以会出现这个问题,是因为==main执行流和信号捕抓执行流,两套不同的执行流,重复进入了同一个函数!==导致了代码结果出现了未定义或者出错的情况!——我们将这种函数称之为==不可重入函数!==

一般我们认为,main执行流和信号捕抓执行流是两套不同的执行流!——虽然在代码里面我们来看也是串行了!main想要后续继续执行,也依旧要等待信号捕抓执行流执行完!但是我们可以看到main执行流其实压根没有直接调用果handler函数!是通过信号过来后才回调过去的!所以我们才说是两套执行流!

如果在main中或者handler中该函数被重复进入后==出现了问题——那就是不可重入函数!(不能重复进入)==

==如果重复进入后没有出现问题!——那么就是可重入函数!==

我们平时用的大部分接口都是不可重入函数!——函数可不可被重入是一个特性!不是一个问题!我们不需要解决

如果一个函数符合以下条件之一则是不可重入的:

  • 调用了malloc或free,因为malloc也是用全局链表来管理堆的
  • 调用了标准I/O库函数。标准标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

volatile

这是一个C语言的的关键字!——不过我们一般很少用!

==这个关键字的作用是保持内存的可见性!——这个说法听起来很抽象!==

我们下面的例子来说明这个

#include <stdio.h>
#include<signal.h>
int quit = 0;

void handler(int signo)
{
    printf("%d 号信号,正在被捕抓\n",signo);
    printf("quit : %d",quit);
    quit = 1;
    printf("-> %d\n", quit);
}
int main()
{
    signal(2,handler);    
    while(!quit);
    printf("注意!该进程是正常退出的!\n");
    return 0;
}

image-20230807204655656

代码运行的结果不出我们所料!

这里我们要说一点就是关于编译器的优化!当我们编译代码的时候,编译器都会进行优化,一般是O1或者O2,编译器的优化都是取决于编译器

我们可以自己手动的将优化级别调高

==当我们手动的提升编译器的优化级别!==

image-20230807210605287

为什么会出现这个现象呢?

数据保存的无法就两个地方——一个是内存,一个是寄存器!那么优化其实也就是将数据从内存优化到寄存器里面!(因为寄存器比内存更快!)

image-20230807214315989

所以这就是为什么明明quit被改了!但是循环却依旧不进行终止的原因!==因为寄存器中quit的存在,遮蔽了物理内存中quit变量存在的事实!==

在while循环只看到了寄存器!而看不到内存!

这种情况就会导致,我们代码没有问题!但是因为编译器的优化从而让代码没有按照预期来进行工作!

==为了解决这个问题!我们就要用到volatile关键字!==

在C/C++的里面,它的作用官方说法就是保持内存可见性!——==用简单的说法就是,让这个数据不要优化到寄存器里面!而是一直从内存里面进行读取!(这就是所谓的保持内存可见性)==

#include <stdio.h>
#include<signal.h>
volatile int quit = 0;

void handler(int signo)
{
    printf("%d 号信号,正在被捕抓\n",signo);
    printf("quit : %d",quit);
    quit = 1;
    printf("-> %d\n", quit);
}
int main()
{
    signal(2,handler);    
    while(!quit);
    printf("注意!该进程是正常退出的!\n");
    return 0;
}

image-20230807215054282

我们依旧进行O3级别的优化!但是此时quit就不会被放进寄存器里面了!代码逻辑也就正常了!

如果出现了信号捕抓执行流和main函数执行流里面有个要修改的值的时候!这时候我们就要注意了!

SIGCHLD

这个信号是和进程等待有关系

如果一个子进程退出了!——那么这个子进程就会变成僵尸状态,然后让父进程读取它,获取子进程的退出码和退出信号!

如果一个子进程没有退出——那么父进程就要阻塞或者非阻塞的等待子进程!

==当子进程退出的时候,它不会直接就退出,然后进入僵尸状态,而是它在进入僵尸状态的时候!它会告诉父进程它的状态!==

==那么它是怎么告诉父进程的呢?——通过发送信号的方式!发送SIGCHLD信号!(17号信号)==

image-20230808095753533

我们可以看到SIGCHLD的行为是Ign(ignore the signal)忽略该信号!这个忽略是内核级别的忽略

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
void Count(int cnt)
{
while(cnt)
{
printf("cnt:%2d\r",cnt);
fflush(stdout);
cnt--;
sleep(1);
}
printf("\n");
}
void handler(int signo)
{
printf("pid: %d , %d 号信号,正在被捕抓\n",getpid(),signo);
}
int main()
{
signal(SIGCHLD,handler);
printf("我是父进程!pid : %d,ppid: %d\n", getpid(), getppid());

pid_t id = fork();
while(id == 0)
{
printf("我是子进程!我要退出了!pid : %d,ppid: %d\n",getpid(),getppid());
Count(5);
exit(1);
} 
while(1) sleep(1);

return 0;
}

image-20230808101930323

==用上面断点我们可以看出来!——子进程退出之后!父进程确实会捕抓子进程发出的信号!==

那么如果知道这一点的话有什么用处呢?

如果不知道这一点,我们想要知道子进程退出什么时候退出!我们只能主动的去调用waitpid和wait这样的函数!——无论是阻塞等待还是非阻塞等待

==但是现在我们知道了!我们其实可以不用去主动关心子进程!等子进程退出了!那么它就会自己给父进程发送信号!这时候父进程再去回收子进程!==

void handler(int signo)
{
//我们是可以在handler里面直接调用wait或者waitpid的!
}

但是这样子代码的健壮性不好!

==情况1:假如我们有很多的子进程!(例如:10个)在同一时刻同时退出==

那么就会向父进程同时发送10个SIGCHLD信号!==但是上面我们讲过当正在处理一个信号的时候,操作系统会自动屏蔽同类型的信号!而且因为pending表只有一份!后续的9个信号也只能被保存一份!==——所以在handler里面直接调用wait或者waitpid是不好的!

所以最好的方式是通过循环等待!——因为我们并不知道有几个进程退出了!所以只能while(1) --> waipid式的等待

waitpid的第一个参数,我们一般都是设置为指定进程的pid!但是也可以设置为-1!即等待任意子进程的进程!

那么这个死循环什么终止呢?——waitpid再也等待不到的时候!说明底层的子进程都全部退出完毕了!

==情况2:我们有很多的子进程(10个),只有一部分退出了!==

这种情况下我们也是进行循环式的等待,假如有5个退出了,当waitpid已经回收了这5个的时候!第六个要不要进程waitpid呢?——==要的!因为进程压根不知道!到底退出了多少个子进程!我们说5个退出了是我们站在上帝视角来看待这件事情的!进程是不知道到底退出了多少个!所以即使将5个子进程都回收!也还是要进行waitpid!==

如果此时是阻塞式等待!那么就出现问题了!——在handler方法里面出现了阻塞式调用!那么就无法返回主进程了!——所以在循环式等待的时候==不能进行阻塞式等待!要进行非阻塞式等待!==——非阻塞式等待时,如果没有等待成功waitpid是会返回0的!

void handler(int signo)
{
    pid_t id;
    while(1)
    {
        id = waitpid(-1,NULL,WNOHANG);
        if(id <= 0)//出错或者等待失败
            break;
    }
}

==这就是通过信号来完成进程等待!==

上面我们都是在说要么在main执行流要么在信号捕抓执行流里面调用wait'/waitpid来回收子进程,我们也可通过==不调用wait/waitpid==的方式来回收子进程!

要想不产生僵尸进程还有另外一种办法==:父进程调用sigaction将SIGCHLD的处理动作置为SIG_IGN(忽略)==

这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。

系统默认的忽略动作和用户用sigaction函数自定义的忽略通常是没有区别的

但这是一个特例此方法对于Linux可用,但不保证 在其它UNIX系统上都可 用。请编写程序验证这样做不会产生僵尸进程

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
void Count(int cnt)
{
    while(cnt)
    {
        printf("cnt:%2d\r",cnt);
        fflush(stdout);
        cnt--;
        sleep(1);
    }
    printf("\n");
}
int main()
{
    signal(SIGCHLD,SIG_IGN);//显示的设置!对SIGCHLD进行忽略
    printf("我是父进程!pid : %d,ppid: %d\n", getpid(), getppid());

    pid_t id = fork();
    while(id == 0)
    {
        printf("我是子进程!我要退出了!pid : %d,ppid: %d\n",getpid(),getppid());
        Count(3);
        exit(1);
    } 
    while(1) sleep(1);

    return 0;
}

image-20230808114133130

但是为什么要我们手动去显示将SIGCHLD设置为SIG_IGN?——我们上面看到过、

image-20230808115726132

该信号的默认行为不就是IGN吗?——**默认的IGN和我们手动设置出来的是不一样的!**默认IGN行为就是我们看到的,如果没有被父进程回收那么就进入僵尸!

但是我们的自己设置的IGN就是不进入僵尸,也不等待,直接回收!

这两个值在操作系统内部肯定是不一样的!

标签:九千,int,进程,屏蔽,handler,信号,linux,字长,include
From: https://blog.51cto.com/u_15835985/9195041

相关文章

  • [转帖]修改Linux内核参数,减少TCP连接中的TIME-WAIT
    https://www.cnblogs.com/xiaoleiel/p/8340346.html 一台服务器CPU和内存资源额定有限的情况下,如何提高服务器的性能是作为系统运维的重要工作。要提高Linux系统下的负载能力,当网站发展起来之后,web连接数过多的问题就会日益明显。在节省成本的情况下,可以考虑修改Linux的内......
  • linux网络磁盘映射到windows
    在linux中按照以下步骤执行第一步:安装samba套件:sudoapt-getinstallsambasudoapt-getinstallsmbclient第二步:修改配置文件:sudovim/etc/samba/smb.conf第三步:添加参数设置:​ 可以在末尾添加[zxc]comment=VMwareUbuntuSharepath=/home/zxc/data......
  • 初学者的嵌入式 Linux 计划!
    俗话说万事开头难,刚开始的时候,是不是根本就不知如何开始?今天给大家分享一个嵌入式大神总结的Linux学习计划!希望给大家提供帮助,;另外想要系统学习也可以dd我!第一阶段:嵌入式硬件基础以及裸机程序开发arm处理器基础知识,工作模式,寄存器,中断与异常,寻址方式,汇编指令集; 熟悉ADS集成开发环......
  • Linux创建运维用户和用户组
    在Linux系统中,为了安全和管理的便利性,建议创建专用的运维用户和用户组,而不是直接使用root用户进行操作。以下是创建运维用户和用户组的步骤:创建用户组使用以下命令创建一个名为appworkergroup的用户组:sudogroupaddappworkergroup创建用户接着,创建一个名为appwork......
  • 35道必懂的 Linux 运维面试题
    Linux运维面试题,给大家参考下~1、现在给你三百台服务器,你怎么对他们进行管理?管理3百台服务器的方式:1)设定跳板机,使用统一账号登录,便于安全与登录的考量。2)使用salt、ansiable、puppet进行系统的统一调度与配置的统一管理。3)建立简单的服务器的系统、配置、应用的cmdb信息管理。......
  • elasticsearch linux 上安装
    1、下载安装包,放到服务器指定目录下: 2、解压,到指定文件夹命令tar -xzf elasticsearch-8.11.3-linux-x86_64.tar.gz3、创建用户并授权:sudouseraddessudopasswdes用户授权指定目录权限: chown-Res:es/opt/module/es-7.8.0 给新创建的普通用户设置sudo权限  ......
  • Linux安装MySQL5.7并设置systemd方式管理
    1、安装前检查1)检查linux系统是否安装过mysql#检查系统中有无安装过mysql,如果有就卸载rpm-qa|grepmysqlrpm-e--nodepsmysql-xxxx#检查所有mysql对应的文件夹,全部删除whereismysqlfind/-namemysqlrm-rf......#删除mysql的配置文件rm-rf/etc/my.cnf#删......
  • v4l2(vedio for linux two)
    //Video设备又分为主设备和从设备对于Camera来说,主设备:CameraHost控制器为主设备,负责图像数据的接收和传输,从设备:从设备为CameraSensor,一般为I2C接口,可通过从设备控制Camera采集图像的行为,如图像的大小、图像的FPS等。//V4L2的主设备号是81,次设备号范围0~255视频设备(次设备......
  • Linux之GDB调试(一)
    一、C++调试准备工作调试代码:#include<iostream>intmain(intargc,char**argv){intiTest=100;constchar*str="thisisatest";std::cout<<"iTestis"<<iTest<<",stris"<<str&l......
  • 在Linux中清理Buff/cache
    在Linux中,缓冲区和缓存是为提高系统性能而保留的,但如果这些缓存过多,可能会消耗大量内存,影响系统的性能。有时候,您可能需要手动清理这些缓存以释放内存。但请注意,通常不建议定期或频繁地这样做,因为这样做可能会对系统性能产生负面影响。以下是清理buff/cache的几种方法:使用......