linux系统编程之三
一)进程1
1) pstree,查看进程树
2)umask,是打印当前程序的umask;为用户文件创建权限掩码;
3) 在shell下输入命令可以运行一个程序,是因为shell进程在读取用户输入命令的命令之后,会调用fork复制出一个新的shell进程;
4) fork创建一个子进程,说是叫子进程,其实就是复制一份父进程,除了进程号不一样;用返回值分辨谁是父,谁是子;
5)refer to as:称作,称为的意思;
6)当子进程结束,会有尸体,这个尸体会由父进程收尸;
//子进程和父进程的简单案例:
#include<sys/types.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<unistd.h>
#include<fcntl.h>
int main(int argc, char** argv)
{
//调用fork会进入内核空间,
pid_t pid = fork(); //理论上返回的应该是孩子id,叫childpid,简写为pid;
int n;
const char* message;
if(pid < 0) //子进程从这里就开始判断了
{
perror("pid create");
exit(1);
}
if(pid == 0)
{
n = 6;
message = "son process\n";
}
else
{
n = 3;
message = "parent process\n";
}
while(n > 0)
{
n--;
printf(message);
sleep(1); //调用sleep,程序进入内核空间,当睡眠时间到,程序会进入就绪态,等待cpu调度,
//当cpu调度程序的时候,程序又返回用户空间,开始运行程序;
}
return 0;
}
进程二)
1)getpid,getppid的学习,跟fork返回的pid毛关系都没有;
ps aux|grep 18878; //ps aux是显示所有进程,grep 18878是搜索是否有这个18878进程;
#include<sys/types.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<unistd.h>
#include<fcntl.h>
int main(int argc, char** argv)
{
//调用fork会进入内核空间,
pid_t pid = fork(); //理论上返回的应该是孩子id,叫childpid,简写为pid;
int n;
if(pid < 0) //子进程从这里就开始判断了
{
perror("pid create");
exit(1);
}
if(pid == 0)
{
n = 6;
while( n > 0)
{
n--;
//只有在这里可以得到子进程的id,别无它法;
printf("child self id is %d, parent = %d\n",getpid(),getppid()); //父进程是fork创建后大于0的那个进程
sleep(1);
}
}
else
{
n = 3;
while( n > 0)
{
n--;
printf("parent self id is %d, parent = %d\n",getpid(),getppid()); //父进程是shell的进程
sleep(1);
}
}
return 0;
}
2)循环创建10个子进程案例
#include<sys/types.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
//重点: 只让父进程循环创建子进程,不能让子进程再次创建子进程;
int main(int argc, char** argv)
{
for(int i = 0; i< 10; i++)
{
pid_t pid = fork();
if(pid < 0)
{
perror("pid create");
exit(1);
}
if(pid == 0) //子进程必须break,否则子子孙孙,无穷匮也;
{
printf("i is %d,child self id is %d, parent = %d\n",i,getpid(),getppid()); //父进程是fork创建后大于0的那个进程
break; //如果不终止,则创建出来的子进程也会参与循环创建,理论上子进程个数为10*9*...*1这么多个;
}
else //父进程直接continue;
{
continue;
}
}
return 0;
}
//输出结果如下:
i is 0,child self id is 19469, parent = 19468
i is 1,child self id is 19470, parent = 19468
i is 2,child self id is 19471, parent = 19468
i is 3,child self id is 19472, parent = 19468
i is 4,child self id is 19473, parent = 19468
i is 5,child self id is 19474, parent = 19468
i is 6,child self id is 19475, parent = 19468
i is 7,child self id is 19476, parent = 19468
i is 8,child self id is 19477, parent = 19468
i is 9,child self id is 19478, parent = 19468
3)gdb多进程调试及exec函数的用法
exec中'l'是list的意思,参数为不定参数;
exec中'v'是vector的意思,是定参的,是数组;
'p'是路径的意思;
'e'是环境变量表需要传进来;
set follow-fork-mode child/parent //模式设置
当进程调用exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新的程序的启动例程开始执行;
execve是真正的可执行程序函数;
environ英 [ɪn'vaɪərən] v 包围,环绕;
getenv和setenv函数的用法及案例:
#include<sys/types.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<unistd.h>
#include<fcntl.h>
int main(int argc, char** argv)
{
// extern char** environ; //引入一个已经定义的变量
// for(int i = 0; environ[i] != NULL; i++)
// {
// printf("%s\r\n",environ[i]); //打印环境变量值
// }
char* path = getenv("PATH"); //从当于从父进程的环境变量里面复制了一份,传到这里;
printf("[%s]\r\n",path); //加一个中括号,就知道从哪里开始,到哪里结束
setenv("PATH","helllo",1); //主进程环境变量没有改,改的是子进程的环境变量
path = getenv("PATH");
printf("[%s]\r\n",path);
return 0;
}
4)exec函数的使用方法:
#include<sys/types.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<unistd.h>
#include<fcntl.h>
int main(int argc, char** argv)
{
//带'/',则理解为路径;不带'/',则到$PATH这个路径下找;
//第二个不定长参数:主函数传参的所有参数都列出来,注意,第0个参数也要传进来,对与"ls",来说,就是ls,
execlp("/bin/ls", "hahaha", "-a", "-l", NULL); //hahaha这个参数没有任何用,写空也行,hahaha应该是ls参数,尽管它没有作用;
perror("exec"); //这句话不会执行
exit(1);
return 0;
}
5)自己实现流的重定向
读一个文件,实现小写转大写,并把转完的数据写入到另一个文件中,包括5.1和5.2两个程序;
5.1)getchar函数的使用方法;
#include<stdio.h>
#include<unistd.h>
#include<ctype.h>
//测试程序: ./target < test.txt //从文件读数据,'<' 相当于重定向;
int main(int argc, char** argv)
{
int ch;
while((ch = getchar()) != EOF) //getchar()函数的作用是从计算机终端(一般为键盘)获取一个无符号字符;
{
putchar(toupper(ch));
}
return 0;
}
5.2)输入、输出重定向,把标准输入和标准输出重定向为对文件的操作;
#include<stdio.h>
#include<unistd.h>
#include<ctype.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/types.h>
#include<stdlib.h>
//输入、输出重定向,把标准输入和标准输出重定向为对文件的操作;
int main(int argc, char** argv)
{
if(argc < 2)
{
printf("cmd + inputfile + outputfile\r\n");
return 1;
}
int fd = open(argv[1],O_RDONLY);
if(fd < 0)
{
perror("open read");
exit(1);
}
dup2(fd,0); //修改了标准输入流的位置,相当于0取代了3,言外之意就是,从标准输入得到的数据,改为从打开的文件中读取;
close(fd); //因为这里关闭了
fd = open(argv[2],O_WRONLY | O_CREAT, 0644); //如果没有文件,就创建一个
if(fd < 0)
{
perror("open write");
exit(1);
}
//下面代码的意思是:按道理应该打印到屏幕上(标准输出),dup2重定向后,把数据写到文件中去了。
dup2(fd,1); //修改了标准输出流的位置,相当于1取代了3; 因为上面关闭了,所以还是3
close(fd);
execl("./upper","./upper",NULL);
perror("exec");
return 0;
}
6)wait和waitpid的作用,是收尸的
一个程序运行起来,分两块,一个是内核空间,一个是用户空间;
defunct 美 [dɪˈfʌŋkt] 失效的
僵尸进程不能被kill -9 ps_id 杀死,相当于是已经死了,杀尸体屁用没有;
如果父进程被杀死了,子进程就会被孤儿院收尸了。
//父进程等待子进程退出的简单案例:
#include<sys/types.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<unistd.h>
#include<wait.h>
int main(int argc, char** argv)
{
pid_t pid = fork(); //一上电就先创建两个进程;
int n = 0;
if(pid < 0)
{
perror("pid create");
exit(1);
}
if(pid == 0) //子进程
{
n = 3;
while( n > 0)
{
n--;
printf("child self id is %d, parent = %d\n",getpid(),getppid()); //父进程是fork创建后大于0的那个进程
sleep(1);
}
exit(3); //这里正常退出
}
else //父进程调用waitpid等待子进程退出,这样就会变成同步机制(没有调用wait的时候,是异步机制)
{
int stat_val;
waitpid(pid,&stat_val,0);
if(WIFEXITED(stat_val)) //正常退出,打印出状态码 //WIFEXITED 定义格式:wait if exited的缩写
{
printf("child exited wist code %d\n",WEXITSTATUS(stat_val));
}
else if(WIFSIGNALED(stat_val)) //信号退出,打印出状态码
{
printf("signal teminal %d\n",WTERMSIG(stat_val));
}
}
return 0; //这里返回0,会被exit读到;
}
输出结果:
child self id is 6638, parent = 6637
child self id is 6638, parent = 6637
child self id is 6638, parent = 6637
child exited wist code 3
7)pipe的学习,父进程写数据,子进程读数据的案例:
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<unistd.h>
#include <fcntl.h>
#include <wait.h>
int main(int argc, char** argv)
{
pid_t pid;
int fd[2];
if(pipe(fd) < 0) //创建管道
{
perror("pipe");
exit(1);
}
pid = fork(); //创建进程
if(pid < 0)
{
perror("fork");
exit(1);
}
//相当于父进程有一个fd[2]数组,子进程还有一个fd[2]数组;
if(pid > 0)
{
close(fd[0]); //关闭读端
write(fd[1],"hello world\n",11);
wait(NULL); //父进程等待子进程结束,给子进程收尸
}
if(pid == 0)
{
char buf[120];
close(fd[1]); //关闭写端;
sleep(2); //不着急读,让子弹飞一会
int n = read(fd[0],buf,20);
// printf("buf is %s",buf);
write(1,buf,n); //等价于printf
}
return 0;
}
//在终端输入,用“echo $?”命令,查看程序的退出状态;
8)popen和pclose的学习
popen函数的功能,创建一个管道,调用fork产生一个子进程,执行一个shell命令来开启一个进程;
8.1)popen read的使用;
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<ctype.h>
int main(int argc, char** argv)
{
FILE* fp;
fp = popen("ls -l","r"); //r或者w,只能选一种
if(!fp)
{
perror("popen");
exit(1);
}
int c;
while((c = fgetc(fp)) != EOF) //从终端输出的数据,读到文件fp中,并转成大写打印到屏幕上;
{
putchar(toupper(c));
}
pclose(fp);
return 0;
}
8.2)popen write的使用;
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<ctype.h>
int main(int argc, char** argv)
{
FILE* fp; //前提要有upper这个程序;
fp = popen("./upper","w"); //往fp上write,等价于往upper上write;
if(!fp)
{
perror("popen");
exit(1);
}
int n = 5;
while(n > 0)
{
n--;
fprintf(fp,"hello world,%d\r\n",n);
}
pclose(fp);
return 0;
}
9)有名管道的读写操作
先用mkfifo命令创建一个管道,如mkfifo aa,利用这个aa进行操作;
9.1) fifo有名管道的的读操作
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char** argv) //read
{
char buf[20];
int fd = open("./aa",O_RDONLY);
if(fd < 0)
{
perror("open");
exit(1);
}
ssize_t n = read(fd,buf,20);
write(1,buf,n); //屏幕上输出
close(fd);
return 0;
}
9.2) fifo有名管道的的写操作
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char** argv) //read
{
int fd = open("./aa",O_WRONLY); //卡主到这里,打开管道的时候
if(fd < 0)
{
perror("open");
exit(1);
}
write(fd,"hello world\n",12); //把文件写到fifo中去;
close(fd);
return 0;
}
上面打开任何一个读操作和写操作,都会阻塞,只有成对出现才可以;
10)共享内存(创建的ipc共享内存,可以用ipcs命令进行查看) ipcs -> ipc show的意思;
ipc相关命令:
ipcs -m,只查看共享内存的;
ipcrm -m 65538(shmid),删除一个共享内存
例如:0x63056ad9 32771 jiang 664 20 0
key shmid owner perms bytes nattch status
0x00000000 32768 jiang 600 524288 2 dest
上面两条信息中, perms是权限的意思,nattch->number attch多少个程序共享了;
criteria 美 [kraɪ'tɪriə] 原则,标准
//共享内存简单案例分析:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<sys/ipc.h>
#include<sys/shm.h>
//ipc-共享内存有句柄的意思;
int main(int argc, char** argv)
{
key_t key = ftok("./test.txt",99); //key是一个索引值,根据hash算法得到的;
printf("key is %#x\n",key);
//把用户空间的20个字节,映射到内核空间;
int shmId = shmget(key,20,IPC_CREAT | 0664); //向内核申请一块空间,并返回共享内存的id;跟open打开一个文件一样;
if(shmId < 0)
{
perror("shmget");
exit(1);
}
printf("shmId = %d\n",shmId); //打印共享内存文件描述符,类似文件描述符fd;
//创建粘连关系
void* shmp = shmat(shmId,NULL,0); //把创建的共享内存(从内核创建的),映射到我们的用户空间;shmat -> shared memory attach的意思;
if(shmp < 0)//不成功
{
perror("shmat");
exit(1);
}
printf("shmp = %p\n",shmp); //得到内核返回来的空间地址
char* cp = (char*)shmp;
//下面两条语句是互斥的,相当于两个程序
// snprintf(cp,20,"123456\n"); //向共享内存(内核开辟的存储空间)写入数据
printf("%s",cp); //读共享内存里面的数据
//取消粘连关系
shmdt(shmp); //取消映射关系
return 0;
}
11)多进程利用共享内存进行通信
shmctl这个函数没有用到,用的是ftok,fork,shmget,shmat,shmdt等系统函数;
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<string.h>
#include<wait.h>
//ipc-共享内存有句柄的意思;
int main(int argc, char** argv)
{
key_t key = ftok("./test.txt",99); //key是一个索引值,根据hash算法得到的;
printf("key is %#x\n",key);
//虽然你想开辟一个20byte的大小,但系统还是会给你开辟一个4096大小的空间
int shmId = shmget(key,20,IPC_CREAT | 0664); //向内核申请一块空间,并返回共享内存的id;跟open打开一个文件一样;
if(shmId < 0)
{
perror("shmget");
exit(1);
}
printf("shmId = %d\n",shmId); //打印共享内存文件描述符,类似文件描述符fd;
//创建粘连关系
void* shmp = shmat(shmId,NULL,0); //把创建的共享内存(从内核创建的),映射到我们的用户空间;shmat -> shared memory attach的意思;
char* charp = (char*)shmp;
if(shmp < 0)//不成功
{
perror("shmat");
exit(1);
}
printf("shmp = %p\n",shmp); //得到内核返回来的空间地址
bzero(charp,100); //保证前100个字符是空的
pid_t pid = fork();
if(pid < 0)
{
perror("fork");
exit(1);
}
if(pid > 0) //父进程
{
while(1)
{
scanf("%s",charp); //scanf是阻塞读,把读到的数据存放到共享内存中去了
if(!strcmp(charp,"quit"))
{
break;
}
}
wait(NULL); //退出后,等待给儿子收尸
}
else //子进程
{
while(1)
{
if(!strcmp(charp,"quit"))
{
break;
}
if(*charp) //如果是空,就等待;不是空,就打印里面的内容;
{
printf("child read is %s\n",charp);
bzero(charp,100);
}
sleep(1); //禁止高速运转判断
}
}
shmdt(shmp);
return 0;
}
12)消息队列(系统内核维护了一个存放消息的队列)
作为一个程序员,应该对边界问题比较敏感!这是一个合格的程序员的标准;
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.h>
#include<wait.h>
//消息队列,相当于晾衣架那样,一个人把东西一个一个挂上去,另外一个人从另一端拿走;
//发消息相当于把衣服挂到队列上,读消息相当于把衣服拿走;
//消息队列的操作和共享内存的操作,几乎是一样的;
//注意3个系统函数,msgget,msgsnd,msgrcv;
#define msglen 20
typedef struct myMsg
{
long mtype;
char mtext[msglen];
}msg_t;
int main(int argc, char** argv)
{
key_t key = ftok("./test.txt",19); //key是一个索引值,根据hash算法得到的;
printf("key is %#x\n",key);
int mqid = msgget(key,IPC_CREAT | 0666);
if(mqid < 0)
{
perror("ipc queue");
exit(1);
}
printf("mqid is = %d\n",mqid);
msg_t buf;
//下面的发送数据和接收数据其实是两个程序,是互斥的,需要编译两次;
//发消息队列到内核空间
// buf.mtype = 1; //存储第一个类型数据
// strncpy(buf.mtext,"hello world\n",msglen);
// msgsnd(mqid,&buf,msglen,0);
// buf.mtype = 2; //存储第二个类型数据
// strncpy(buf.mtext,"online\n",msglen);
// msgsnd(mqid,&buf,msglen,0); //写完的数据,可以用"ipcs -q"查询数据,是否正确写到消息队列中去了;
//从内核空间读数据到用户空间
//如果一个类型的消息读完,不同类型的消息也是不能瞎读的,根据类型读,即使有消息,不同类型的消息也是不能读走;
//如果没有该类型的消息,就会阻塞到这里;
msgrcv(mqid,&buf,msglen,1,0); //取走第1个类型信息,取消息也是根据类型type取消息
printf("msg type is: %ld, msg text is: %s",buf.mtype,buf.mtext);
msgrcv(mqid,&buf,msglen,2,0); //取走第2个类型信息
printf("msg type is: %ld, msg text is: %s",buf.mtype,buf.mtext);
return 0;
}
//读消息队列的输出结果
key is 0x13056ad9
mqid is = 4
msg type is: 1, msg text is: hello world
msg type is: 2, msg text is: online