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.15.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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值