#深入理解System V原理

深入理解System V原理

1. sysem v消息队列

------------------------------------------------
首先,系统中的消息队列,在内核中会维护一个叫msqid_ds的信息结构

struct msqid_ds {
               struct ipc_perm msg_perm;     /* Ownership and permissions */
               struct msg      *msg_frist    /* ptr to first message on queue */
               struct msg      *msg_last     /* ptr to last message on queue */
               time_t          msg_stime;    /* Time of last msgsnd(2) */
               time_t          msg_rtime;    /* Time of last msgrcv(2) */
               time_t          msg_ctime;    /* Time of last change */
               unsigned long   __msg_cbytes; /* Current number of bytes in
                                                queue (nonstandard) */
               msgqnum_t       msg_qnum;     /* Current number of messages
                                                in queue */
               msglen_t        msg_qbytes;   /* Maximum number of bytes
                                                allowed in queue */
               pid_t           msg_lspid;    /* PID of last msgsnd(2) */
               pid_t           msg_lrpid;    /* PID of last msgrcv(2) */
           };


这个消息队列的链表中,维护这三个消息,消息类型分别为100,200,300. 长度为1,2,3字节。
具体这个消息是如何定义的,我们之后在谈。
看到了这个结构后就可以开始理解后面所有的函数的作用了。

int msgget(key_t key,int oflag);

这个函数的参数和返回值就在这里,不在累赘。
当你用msgget函数创建一个消息队列的时候,在内核中,会对msqid_ds进行初始化。其中有一个叫ipc_perm这个结构体就不得不提起来。

struct ipc_perm
{
    key_t            key;                        /*ipc的key值*/
    uid_t            uid;                       /*共享内存所有者的有效用户ID */
    gid_t            gid;                       /* 共享内存所有者所属组的有效组ID*/
    uid_t            cuid;                     /* 共享内存创建 者的有效用户ID*/
    gid_t            cgid;                     /* 共享内存创建者所属组的有效组ID*/
    unsigned short   mode;               /* Permissions + SHM_DEST和SHM_LOCKED标志*/
    unsignedshort    seq;                  /* 序列号*/
};

其中uid和cuid,gid和cgid会根据当前进程的用户和用户组ID进行初始化
oflag对应的权限会放在mode中,key即为传入的key;
在消息队列ds中一切与操作(msgsnd msgget)有关的会被初始化为0;
讲当前时间放入c_time中 msg_qbytes设置为系统限制值。

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

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

这两个函数密不可分,所以我放在一起说,看名字就知道一个是发送消息,一个是接受消息。
在这里我们需要自己定义自己的结构体来传递消息。

struct mesg
 {
    long mesg_tpye;
      char mesg_date[BUF_SIZE];
 };

在自定义的结构体的时候要注意,第一个成员一定要是long类型的tpye成员,用于存放消息类型。用我自定义的结构体为例子,msgsnd的第二个应该为mesg的指针,第三个应该为strlen(mesg_date)+1。那么来一个更通用的列子吧。

struct mesg
 {
    long mesg_tpye;
    int  mesg_short;
      char mesg_date[BUF_SIZE];
 };

在这里有两个数据需要传递,那么长度应该怎么写呢?可以是sizeof(mesg) - sizeof(long);
所以这个长度,应该是你要发送的数据长度。也就是再个下图的消息进行初始化,不是吗?

在msgrcv中size_t length是一个期望值,就是表示自己可以接受的最大长度的数据。所以你可以用一个宏的长度来接受。
接下来我们继续关注oflag这个参数,这个参数有两个值0和IPC_NOWAIT。
特别注意,在指定非阻塞的情况下,有多种情况可能返回使函数立即返回。
1.消息队列中已经有太多的字节,注意msq_qbytes规定了消息队列的最大字节数。
2.系统中有过多的消息。
这是将返回一个EIDGAIN;
若指定了0,线程就会投入睡眠。如果msqid被删除会返回EIDRM,直到有新的存放空间时醒来。

在msgrcv中有一个type,也需要注意。
type = 0的时候,就直接取出msg_first指向的第一个消息。
type > 0的时候,取出类型为type的第一消息。
type < 0的时候,取出第一个类型小于等于type的绝对值的消息。
注意当接受消息指定的len小于传入的消息的字节数的时候,会返回一个E2BIG的错误。当在oflag中指定了MSG_NOERROR将会截断数据!

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

cmd一共有三个参数:
1.IPC_RMID 用于删除指定的消息队列,而且当前队列上的所有消息将会被丢弃。在指定这个参数的时候,第三个参数被忽略,你可以传入一个NULL;
2.IPC_SET 设置消息队列的属性,要设置的属性需先存储在buf中,可设置的属性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes
3.IPC_STAT:获得msgid的消息队列头数据到buf中
在使用第二参数的时候,需要自己指定一个buff结构对应所需要修改的权限,比如struct ipc_perm info;
同理第三个参数也需要指定一个buff,类型是msqid_ds;

实例代码

这里面有一个用消息队列的服务器的例子。

2. sysem v信号量

------------------------------------------------
首先在内核中会维护一个如下的信息结构。

struct semid_ds {
               struct ipc_perm sem_perm;  /* Ownership and permissions */
               time_t          sem_otime; /* Last semop time */
               time_t          sem_ctime; /* Last change time */
               unsigned long   sem_nsems; /* No. of semaphores in set */
               struct sem      *sem_base; /* ptr to array of sem */
           };

这里面有一个新的成员,值得一提sem_nsems它表示信号量数组的元素的个数。sem_base结构体指针实际上其实是不存在的(就算存在也是指向内核的一部分区域)。

struct sem{
        ushort_t    semval;     /*sem value,nonnegative */
        short       sempid      /*pid of last successful semop(),SETVAL,SETALL */
        ushort_t    semncnt;    /*awating semval >current value */
        ushort_t    semzcnt;    /*awating semval = 0 */
    }

其中semcnt 是等待其值增长的进程数计数,semzcnt是等待其值变0的进程数计数;

在这个图片中nsems等于2共有两个成员,一个用0表示,一个用1表示(下标)。

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

这个函数既可以创建信号量,也可以访问信号量。在创建信号量时候,nsems为要创建的信号量的数量,semflag为其权限:SEM_R和SEM_A分别表示读和改还可以与常见的ipc权限进行按或位。
初始话时与msgget一样初始化semid_ds但是不初始化sem成员结构体。
而且只有在semget创建一个新的信号量的时候,而且成功返回是o_time才会被设置为0。

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

这个函数用与操作sem信号量。
semid 用于指定目标信号量集
sembuf的结构

struct sembuf{
           unsigned short sem_num;  /* semaphore number */
           short          sem_op;   /* semaphore operation */
           short          sem_flg;  /* operation flags */
           };

其中sem_num 用于指定操作第几个信号量, op是操作数, 通常是+1 -1分别对应pv操作。
P(S):①将信号量S的值减1,即S=S-1; ②如果S=0,则该进程继续执行;否则该进程置为等待状态,排入等待队列。
V(S):①将信号量S的值加1,即S=S+1;②如果S>0,则该进程继续执行;否则释放队列中第一个等待信号量的进程。
sem_op > 0 semval加上 sem_op 的值,并且将semadj减去sem_opd的值,表示进程释放控制的资源;
sem_op = 0 如果没有设置 IPC_NOWAIT,相应的semzcnt就增加1,调用进程进入阻塞,直到信号量的semval值为0;否则进程不回睡眠,直接返回 EAGAIN
sem_op < 0 调用者希望semval的值大于或者等于sem_op的绝对值值。如果semval大于sem_op的绝对值,semval加上 sem_op 的值,并且将sem_op的绝对值加到semadj上。如果不大于若没有设置 IPC_NOWAIT ,则调用进程阻塞,直到资源可用;否则进程直接返回EAGAIN
在操作semadj的时候需要sem_flg指定SEM_UNDO。
而且需要注意的是系统保证op操作是一个原子操作,这个非常重要!!!

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

...对应union semun arg*

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

这个公用体在需要在程序中显示的申明!
PC_STAT

从信号量集上检索semid_ds结构,并存到semun联合体参数的成员buf的地址中

IPC_SET

设置一个信号量集合的semid_ds结构中ipc_perm域的值,并从semun的buf中取出值

IPC_RMID

从内核中删除信号量集合

GETALL

从信号量集合中获得所有信号量的值,并把其整数值存到semun联合体成员的一个指针数组中

GETNCNT

返回当前等待资源的进程个数

GETPID

返回最后一个执行系统调用semop()进程的PID

GETVAL

返回信号量集合内单个信号量的值

GETZCNT

返回当前等待100%资源利用的进程个数

SETALL

与GETALL正好相反

SETVAL

用联合体中val成员的值设置信号量集合中单个信号量的值

下面是一些实例代码
代码

3. sysem v共享内存

------------------------------------------------
首先在内核中会维护这样一个结构。

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;   /* Last change time */
               pid_t           shm_cpid;    /* PID of creator */
               pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
               shmatt_t        shm_nattch;  /* No. of current attaches */
               ...
};

除了有一个shm_nattch之外,其他成员都是多次使用。
shm_nattch主要与下面两个函数有关。在此就不在赘述shmget和shmctl它们上面的函数有着共同的特性与使用方法。

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

       int shmdt(const void *shmaddr);

这两个函数分别用于附接和断开进程的地址空间。
shmat
第一个参数,shm_id是由shmget()函数返回的共享内存标识。
第二个参数,shm_addr指定共享内存连接到当前进程中的地址位置,如果shmaddr 是NULL,系统将自动选择一个合适的地址!如果shmaddr 不是NULL 并且没有指定SHM_RND则此段连接到addr所指定的地址上。 3.如果shmaddr非0 并且指定了SHM_RND 则此段连接到shmaddr -(shmaddr mod SHMLAB)所表示的地址上。这里解释一下SHM_RND命令,它的意思是取整,而SHMLAB的意思是低边界地址的倍数,它总是2的乘方,该算式是将地址向下取最近一个SHMLAB的倍数。 除非只计划在一种硬件上运行应用程序(在现在是不太可能的),否则不用指定共享段所连接到的地址。所以一般指定shmaddr为0,以便由内核选择地址。
第三个参数,shm_flg是一组标志位,通常为0。调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1.
shmdt
参数shmaddr是shmat()函数返回的地址指针,调用成功时返回0,失败时返回-1.

这里有一份shm和sem共同作用搭建服务器的例子大家可以参考下,不懂的可以在下面回复我。
点我下载查看代码

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


用的openwrt路由器,家里宽带申请了动态公网ip,为了方便把2280端口映射到公网,发现经常被暴力破解,自己写了个临时封禁ip功能的脚本,实现5分钟内同一个ip登录密码错误10次就封禁这个ip5分钟,并且进行邮件通知使用步骤openwrt为19.07.03版本,其他版本没有测试过安装bashmsmtpopkg
#!/bin/bashcommand1&command2&wait从Shell脚本并行运行多个程序–杨河老李(kviccn.github.io)
1.先查出MAMP下面集成的PHP版本cd/Applications/MAMP/bin/phpls-ls 2.编辑修改.bash_profile文件(没有.bash_profile文件的情况下回自动创建)sudovim~/.bash_profile在文件的最后输入以下信息,然后保存退出exportPATH="/Applications/MAMP/bin/php/php7.2.20/b
1、先输入locale-a,查看一下现在已安装的语言2、若不存在如zh_CN之类的语言包,进行中文语言包装:apt-getinstalllanguage-pack-zh-hans3、安装好后我们可以进行临时修改:然后添加中文支持: locale-genzh_CN.UTF-8临时修改> export LC_ALL='zh_CN.utf8'> locale永久
BashPerlTclsyntaxdiff1.进制数表示Languagebinaryoctalhexadecimalbash2#[0~1]0[0~7]0x[0~f]or0X[0~f]perl0b[0~1]0[0~7]0x[0~f]tcl0b[0~1]0o[0~7]0x[0~f]bashdifferentbaserepresntationreference2.StringlengthLanguageStr
正常安装了k8s后,使用kubect工具后接的命令不能直接tab补全命令补全方法:yum-yinstallbash-completionsource/usr/share/bash-completion/bash_completionsource<(kubectlcompletionbash)echo"source<(kubectlcompletionbash)">>~/.bashrc 
参考这里启动jar包shell脚本修改过来的#!/bin/bash#默认应用名称defaultAppName='./gadmin'appName=''if[[$1&&$1!=0]]thenappName=$1elseappName=$defaultAppNamefiecho">>>>>>本次重启的应用:$appName<
#一个数字的行#!/bin/bashwhilereadlinedon=`echo$line|sed's/[^0-9]//g'|wc-L`if[$n-eq1]thenecho$linefidone<1.txt#日志切割归档#!/bin/bashcd/data/logslog=1.logmv_log(){[-f$1]&&mv$1$2
#文件增加内容#!/bin/bashn=0cat1.txt|whilereadlinedon=[$n+1]if[$n-eq5]thenecho$lineecho-e"#Thisisatestfile.\n#Testinsertlineintothisfile."elseecho$linefidone#备份/etc目录#
# su - oraclesu: /usr/bin/ksh: No such file or directory根据报错信息:显示无法找到文件 /usr/bin/ksh果然没有该文件,但是发现存在文件/bin/ksh,于是创建了一个软连接,可以规避问题,可以成功切换到用户下,但无法执行系统自带命令。$. .bash_profile-ksh: .: .b
history显示历史指令记录内容,下达历史纪录中的指令主要的使用方法如果你想禁用history,可以将HISTSIZE设置为0:#exportHISTSIZE=0使用HISTIGNORE忽略历史中的特定命令下面的例子,将忽略pwd、ls、ls-ltr等命令:#exportHISTIGNORE=”pwd:ls:ls-ltr:”使用HIS
一.命令历史  1.history环境变量:    HISTSIZE:输出的命令历史条数,如history的记录数    HISTFILESIZE:~/.bash_history保存的命令历史记录数    HISTFILLE:历史记录的文件路径    HISTCONTROL:     ignorespace:忽略以空格开头的命令
之前在网上看到很多师傅们总结的linux反弹shell的一些方法,为了更熟练的去运用这些技术,于是自己花精力查了很多资料去理解这些命令的含义,将研究的成果记录在这里,所谓的反弹shell,指的是我们在自己的机器上开启监听,然后在被攻击者的机器上发送连接请求去连接我们的机器,将被攻击者的she
BashOne-LinersExplained,PartI:Workingwithfileshttps://catonmat.net/bash-one-liners-explained-part-oneBashOne-LinersExplained,PartII:Workingwithstringshttps://catonmat.net/bash-one-liners-explained-part-twoBashOne-LinersExplained,PartII
Shell中变量的作用域:在当前Shell会话中使用,全局变量。在函数内部使用,局部变量。可以在其他Shell会话中使用,环境变量。局部变量:默认情况下函数内的变量也是全局变量#!/bin/bashfunctionfunc(){a=99}funcecho$a输出>>99为了让全局变量变成局部变量
1、多命令顺序执行;  命令1;命令2  多个命令顺序执行,命令之间没有任何逻辑联系&&  命令1&&命令2  逻辑与,当命令1正确执行,才会执行命令2||  命令1||命令2  逻辑或,当命令1执行不正确,才会执行命令2例如:ls;date;cd/home/lsx;pwd;who ddif=输入文件of=输
原博文使用Linux或者unix系统的同学可能都对#!这个符号并不陌生,但是你真的了解它吗?首先,这个符号(#!)的名称,叫做"Shebang"或者"Sha-bang"。Linux执行文件时发现这个格式,会把!后的内容提取出来拼接在脚本文件或路径之前,当作实际执行的命令。 Shebang这个符号通常在Unix系统的脚本
1、历史命令history[选项][历史命令保存文件]选项:-c:  清空历史命令-w:  把缓存中的历史命令写入历史命令保存文件 ~/.bash_historyvim/etc/profile中的Histsize可改存储历史命令数量历史命令的调用使用上、下箭头调用以前的历史命令使用“!n”重复执行第n条历史
目录1.Shell脚本规范2.Shell脚本执行3.Shell脚本变量3.1环境变量3.1.1自定义环境变量3.1.2显示与取消环境变量3.1.3环境变量初始化与对应文件的生效顺序3.2普通变量3.2.1定义本地变量3.2.2shell调用变量3.2.3grep调用变量3.2.4awk调用变量3.3
   http://www.voidcn.com/blog/wszzdanm/article/p-6145895.html命令功能:显示登录用户的信息命令格式:常用选项:举例:w显示已经登录的用户及正在进行的操作[root@localhost~]#w 11:22:01up4days,21:22, 3users, loadaverage:0.00,0.00,0.00USER