linux调试工具ipcs的深入分析
1)system v系统共享内存
用ipcs调试共享内存
测试源程序如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/wait.h>
void error_out(const char *msg)
{
perror(msg);
exit(EXIT_FAILURE);
}
int main (int argc, char *argv[])
{
key_t mykey = 12345678;
const size_t region_size = sysconf(_SC_PAGE_SIZE);
int smid = shmget(mykey, region_size, IPC_CREAT|0666);
if(smid == -1)
error_out("shmget");
void *ptr;
ptr = shmat(smid, NULL, 0);
if (ptr == (void *) -1)
error_out("shmat");
pid_t pid = fork();
if (pid == 0){
u_long *d = (u_long *)ptr;
*d = 0xdeadbeef;
exit(0);
}
else{
int status;
waitpid(pid, &status, 0);
printf("child wrote %#lx\n", *(u_long *)ptr);
}
sleep(30);
int r = shmdt(ptr);
if (r == -1)
error_out("shmdt");
r = shmctl(smid, IPC_RMID, NULL);
if (r == -1)
error_out("shmdt");
return 0;
}
编译:
gcc smem.c -o smem
注:这个程序会申请共享内存,父子进程都会向共享内存写数据,达到IPC通讯的目的.
终端1)
./smem
child wrote 0xdeadbeef
终端2)
ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00bc614e 18874397 root 666 4096 1
注:
key栏中列出的信息是应用程序定义的键值,如果是私有对象的键值则为0,在这里我们定义键值为12345678,也就是输出的0x00bc614e(十六进制)
shmid栏中列出共享内存的ID,这个值是唯一的.
owner栏中列出创建共享内存的用户是root.
perms栏中列出共享内存的权限.
bytes栏中列出这块共享内存的大小,我们通过调用sysconf(_SC_PAGE_SIZE)得到要创建的共享内存大小为4096个字节.
nattch栏中列出连接在关联的共享内存段的进程数.
status栏中列出当前共享内存的状态,当该段内存的mode字段设置了SHM_DEST位时就会显示"dest"字样,
当用户调用shmctl的IPC_RMID时,内核首先看有多少个进程还和这段内存关联着,如果关联数为0,就会销毁(释放)这段内存,否则就设置这段内存的mode位SHM_DEST,
并设置它的key为IPC_PRIVATE,这意味着关联着的进程仍可合法存取这端内存,但是它不能再被新的进程关联了.
在上面的输出中,我们没有看到smem用到的共享内存有dest的状态,而此时我们用ipcrm -m 18874397手工删除该段共享内存时,
此时该段的共享内存键值将会是0x00000000(IPC_PRIVATE),而程序通过调用shmdt来释放该段共享内存时,这段共享内存才会真正的消失.
为完成这个测试,我们修改上面的程序,在shmdt()后面增加:
printf("shmdt function run finished\n");
sleep(30);
在shmctl函数后面增加:
printf("shmctl function run finished\n");
终端1,重新编译,运行
gcc smem.c -o smem
./smem
child wrote 0xdeadbeef
终端2
运行ipcs -m查看共享内存,程序进入第一个sleep(30);,此时status为空
ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00bc614e 0 root 666 4096 1
删除shmid为32768的共享内存,此时status为dest,而key变为0x00000000
ipcrm -m 32768
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000000 32768 root 666 4096 1 dest
过30秒后,此时程序运行了shmdt函数释放共享内存,我们用ipcs -m再看不到该共享内存,虽然它没有运行到shmctl(smid, IPC_RMID, NULL);
最后smem再过30秒后,运行了shmctl(smid, IPC_RMID, NULL);删除共享内存,这时会报错shmdt: Invalid argument,因为我们手工删除了共享内存,
又程序到最后再去删除共享内存,所以报错.
我们通过ipcs -mi 32768可以看到更详细的信息,如下:
Shared memory Segment shmid=327680
uid=0 gid=0 cuid=0 cgid=0
mode=0666 access_perms=0666
bytes=4096 lpid=3263 cpid=3263 nattch=0
att_time=Mon Mar 14 09:42:52 2011
det_time=Mon Mar 14 09:43:22 2011
change_time=Mon Mar 14 09:42:52 2011
注:
cuid=0代表创建这个共享内存的用户ID为0
cgid=0代表创建这个共享内存的组ID为0
lpid=3263代表最后一次访问这个共享内存段的PID为3263
cpid=3263代表最后一产创建这个共享内存段的PID为3263
att_time=Mon Mar 14 09:42:52 2011代表最后一次调用shmat()的时间
det_time=Mon Mar 14 09:43:22 2011代表最后一次调用shmdt()的时间
change_time=Mon Mar 14 09:42:52 2011代表最后一次用shmctl()修改共享内存段的时间.
最后system v共享内存的最大值可以通过修改/proc/sys/kernel/shmmax进行调整.
2)system v系统消息队列
用ipcs调试消息队列.
测试源程序如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/msg.h>
#include <sys/ipc.h>
struct message {
long int mtype;
char mtext[128];
};
int send_msg(int qid, int mtype, const char text[]){
struct message msg = {
.mtype = mtype
};
strncpy (msg.mtext, text, sizeof(msg.mtext));
int r = msgsnd(qid, &msg, sizeof(msg), 0);
if (r == -1){
perror("msgsnd");
}
}
void producer(int mqid)
{
send_msg(mqid, 1, "type 1 - first");
send_msg(mqid, 2, "type 2 - second");
send_msg(mqid, 1, "type 1 - third");
}
void consumer(int qid)
{
struct message msg;
int r;
int i;
for (i = 0;i<3; i++){
r = msgrcv(qid, &msg, sizeof(struct message), -2, 0);
printf("'%s'\n", msg.mtext);
}
}
int main (int argc, char *argv[])
{
int mqid;
mqid = msgget (IPC_PRIVATE, S_IREAD|S_IWRITE);
if (mqid == -1) {
perror("msgget");
exit (1);
}
pid_t pid = fork();
if (pid == 0){
sleep(60);
consumer(mqid);
exit (0);
}
else{
int status;
producer(mqid);
wait(&status);
}
int r = msgctl(mqid, IPC_RMID, 0);
if (r)
perror("msgctl");
return 0;
}
编译mesg.c
gcc mesg.c -o mesg
注:这个程序中,父进程会将三条消息发送到消息队列,子进程在等待60秒后,再收接消息.
在60秒中,消息存在于消息队列,以便于我们查看.
执行mesg
./mesg&
ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0x00000000 229376 root 600 408 3
注:
key栏中列出的信息是应用程序定义的键值.
msgid栏中列出的值是系统定义的键值.
正如所期望的,系统定义的键值是唯一的,而在本例中应用程序定义的键值全部是0,这意味着这些消息队列是使用IPC_PRIVATE键值创建的.
owner栏中列出创建消息队列的用户是root.
perms栏中列出这个消息队列的权限.
used-bytes栏中列出这个消息队列所占用的空间大小,在这里我们的结构体:
struct message {
long int mtype;
char mtext[128];
};
long int mtype占用8个字节,因为它是64位系统,如果是32位系统,它占用的字节为4个,
char mtext[128]占用128个字节,也就是一条消息就是136,三条消息正好是408.
messages栏中列出这条消息队列中有几条消息,我们发送了三条消息,所以这里正好是3.
用ipcs -q -i PID的方式可以看到更详细的信息,如下面:
ipcs -q -i 294912
Message Queue msqid=294912
uid=0 gid=0 cuid=0 cgid=0 mode=0600
cbytes=408 qbytes=16384 qnum=3 lspid=4036 lrpid=0
send_time=Fri Mar 11 20:52:21 2011
rcv_time=Not set
change_time=Fri Mar 11 20:52:21 2011
注:
cuid一栏列出创建这个消息队列的用户ID
cgid一栏列出创建这个消息队列的组ID
qbytes一栏列出SYSTEM V消息队列的最大值,可以通过修改/proc/sys/kernel/msgmnb和/proc/sys/kernel/msgmax进行调整.
lspid一栏列出最后一个发送消息到这个消息队列的进程.
lrpid一栏列出最后一个从这个消息队列接收消息的进程.
send_time一栏列出发送消息到这个消息队列的最后时间.
rcv_time一栏列出从这个消息队列接收消息的最后时间.
change_time一栏列出更改这个消息队列的最后时间.
最后可以用ipcrm -q 来删除消息队列
3)system v系统的信号量
用ipcs调试信号量
测试源程序如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/sem.h>
int
main (int argc, char *argv[])
{
key_t semkey = ftok("/tmp", 'a');
int semid =
semget(semkey, 1, IPC_CREAT|IPC_EXCL|S_IRUSR|S_IWUSR);
if(semid != -1){
printf("Created new semaphore\n");
}
else
if(errno == EEXIST){
printf("semaphore exists\n");
semid = semget(semkey, 1, 0);
}
assert(semid != -1);
if (argc == 2){
int op = atoi(argv[1]);
struct sembuf sb={
.sem_num = 0,
.sem_op = op,
.sem_{敏感词} = 0
};
int r = semop (semid,&sb,1);
assert(r != -1);
printf("Operation %d done\n", op);
}
else {
printf("no operation \n");
}
printf("semid %d value %d\n", semid ,semctl(semid,0,GETVAL));
return 0;
}
编译sysv_sem.c
gcc sysv_sem.c -o sysv_sem
注:
这个程序通过semget函数创建了一个信号量集,semop函数操作了信号量集中的一个集号,这样来增加或减少信号量中含的值,从而达到程序同步和资源互斥的目的.
执行程序,此时创建了一个信号量,初始值.sem_num为0,所以它通过semctl函数获取的值为0.
./sysv_sem 0
Created new semaphore
Operation 0 done
semid 196608 value 0
执行程序,将参数换成1,此时它的值为1,如下:
./sysv_sem 1
semaphore exists
Operation 1 done
semid 196608 value 1
用ipcs -s来查看信号量信息,如下:
ipcs -s
------ Semaphore Arrays --------
key semid owner perms nsems
0x61018001 196608 root 600 1
注:
key栏中列出的信息是应用程序定义的键值,这里我们用ftok来生成它的ID.
semid栏中列出系统定义的键值.
owner栏中列出创建该信号量集的用户是root
perms栏中列出这个信号量集的权限.
nsems栏中列出这个信号量集中指定了多少个信号量,我们的例子中指定了1个,可以通过semget函数指定多个,如:
segmet(semkey, 5, IPC_CREAT|IPC_EXCL|S_IRUSR|S_IWUSR);
这样就在这个信号集中指定了5个信号量.
用-i参数可以看到更详细的信息,如下;
ipcs -s -i 196608
Semaphore Array semid=196608
uid=0 gid=0 cuid=0 cgid=0
mode=0600, access_perms=0600
nsems = 1
otime = Tue Mar 15 11:44:49 2011
ctime = Tue Mar 15 11:43:38 2011
semnum value ncount zcount pid
0 1 0 0 2791
注:
cuid=0列出创建这个信号量集的用户ID.
cgid=0列出创建这个信号量集的组ID.
mode=0600列出创建这个信号量集时的权限.
access_perms=0600列出这个信号量集的访问权限.
otime = Tue Mar 15 11:44:49 2011列出这个信号量集的访问操作时间,如semop函数对信号量集的操作.
ctime = Tue Mar 15 11:43:38 2011列出这个信号量集的创建时间,如semget函数创建这个信号量集.
semnum列出了信号量集中信号量的序列,如果我们在semget函数中指定了两个信号量,这里的输出,将会是下面的信息:
semnum value ncount zcount pid
0 8 0 0 3270
1 0 0 0 0
ncount列出等待信号量增加的进程的个数.
例如我们指定op为负值,此时负值的绝对值大于当前的信号量值,这时将会阻塞,也就是等待资源的进程数会增加,如下:
./sysv_sem -6
semaphore exists
此时阻塞.
我们在另一个终端下查看当前的信号量集,如下:
ipcs -s -i 425984
Semaphore Array semid=425984
uid=0 gid=0 cuid=0 cgid=0
mode=0600, access_perms=0600
nsems = 2
otime = Tue Mar 15 12:14:06 2011
ctime = Tue Mar 15 12:08:15 2011
semnum value ncount zcount pid
0 0 1 0 3337
1 0 0 0 0
此时等待信号量增加的进程个数为1,即ncount为1,表示有一个进程等待信号量值增加.
zcount列出正在等待信号量变成零的进程的个数
例如我们使当前的信号量值大于0,此时指定op的值为0,这时将会阻塞,直到这个信号量变为0,在阻塞期间等待信号量变成零的进程个数就是zcount,如下:
增加信号量值为1.
./sysv_sem 1
semaphore exists
Operation 1 done
semid 425984 value 0
再次运行sysv_sem程序,指定op为0
./sysv_sem 0
semaphore exists
此时阻塞.
我们在另一个终端查看当前的信号量集,如下:
ipcs -s -i 425984
Semaphore Array semid=425984
uid=0 gid=0 cuid=0 cgid=0
mode=0600, access_perms=0600
nsems = 2
otime = Tue Mar 15 12:23:19 2011
ctime = Tue Mar 15 12:08:15 2011
semnum value ncount zcount pid
0 1 0 1 3499
1 0 0 0 0
此时等待信号量变成零的进程个数为1,即zcount为1,表示有一个进程等待信号量值变为零.
最后我们可以修改/proc/sys/kernel/sem,来达到修改信号量最大数及相关限制的目的.
例如:
cat /proc/sys/kernel/sem
250 32000 32 128
第一列,表示每个信号集中的最大信号量数目.
第二列,表示系统范围内的最大信号量总数目.
第三列,表示每个信号发生时的最大系统操作数目.
第四列,表示系统范围内的最大信号集总数目.