printf()

输出缓冲区

  • 缓冲区满

  • 强制刷新缓冲区 “\n",fflush()

  • 程序结束

    #include<stdio.h>
    #include<unistd.h>
    #include<stdlib.h>
    #include<string.h>
    int main()
    {
        printf("hello,world!!!\n");
        //fflush(stdout);
        sleep(3);
        
        exit(0);//退出大小当前进程
    }

库函数

  • 声明:stdio.h /usr/include

  • 实现:libc.a libc.so

静态库,共享库

/lib ,usr/lib

win:静态库.lib,动态链接库 .dll (Dynamic Link Library)

Linux:静态库.a(Archive 档案文件),共享库.so(shared object)

:预先编译好的方法的集合 add.c----->add.o/add.obj

静态库

archiver(归档库,存储器) c(create创建) r(replace 替换) v(Verbose)详细模式

共享库

sudo su--->切换管理员
mv libfoo.so /lib
gcc -o main main.c -l foo(-L---->已经默认是在 标准位置/lib)
ldd main---->main可执行程序中所用的共享库 (List Dymanic Dependencies)共享库依赖分析工具

静态库和共享库的区别:

gcc -o main main.c -L. -lfoo    静态库
大   包含(拷贝)了用到的foo库中的方法      ---->删除库文件,main可以正常运行
gcc -o main main.c -L. -lfoo  共享库(优先)-->在程序运行时动态链接
小  没有包含(拷贝)了用到的foo库中的方法,只做标记        删除库文件,main无法执行
                                                   ldd查看可执行文件用到哪些共享库
                          LD_LIBRARY_PATH环境变量,可以指定可执行程序链接库文件的位置
                          eg:LD_LIBRARY_PATH=.   ---> "."代表当前位置
                             export LD_LIBRARY_PATH

环境变量:

操作系统中用于存储系统运行状态程序配置信息动态参数------>本质是“键值对”(PATH=/usr/bin),供系统和应用程序读取

共享配置信息

将某个文件夹的路径添加到全局都可以访问的状态:用户环境变量、系统环境变量

  • 用户变量:当前电脑登录的用户全局都可以访问

  • 系统环境变量:一台电脑的多个用户全局都可以访问

进程fork()复制进程

《操作系统》:管理计算机软硬件资源,为用户提供一个交互的接口

进程、内存、文件管理(文件系统) 中断

进程:一个正在运行的程序

PCB:进程控制块 Linux进程描述符

fork()复制内存,以为单位复制,写时拷贝(延时/免除拷贝)---->修改时才复制,否则共享

内核为了提高fork()复制效率---->写时拷贝

父进程的fork()返回值----->子进程pid

子进程fork()返回值---->0

引用头文件#include<unistd.h>

pid_t pid=fork()

getpid()--->当前进程的pid

getppid()---->父进程pid

#include <stdio.h>   
#include <stdlib.h>   
#include <unistd.h>   
#include <string.h>   
#include <assert.h>       
int main(int argc, char* argv[],char* envp[])   
{      
    char * s = NULL;   
    int n = 0;        
    pid_t pid = fork();   
    assert( pid != -1 ); 
    if ( pid == 0 )   
     {   s = "child";   
         n = 3;   
     }   
    else   
     {   
         s = "parent";   
         n = 7; 
     }         
    int i = 0;   
    for(; i < n; i++ )        
    {   
        printf("cur_pid=%d,p_pid=%d,s=%s\n",getpid(),getppid(),s); 
        sleep(1);  
     }   
    exit(0);   
 }  
//n的逻辑地址相同,物理地址不同(页面 2^32/2^12=2^20---->4K(2^12))
nm +程序名---->查看逻辑地址
逻辑地址、物理地址(一般不可以直接指定)
printf

面试题:

#include <stdio.h>   
#include <stdlib.h>   
#include <unistd.h>   
#include <string.h>   
#include <assert.h>       
int main(int argc, char* argv[],char* envp[])   
{      
    fork()||fork();// fork()&&fork();
    printf("A\n");//3个A  
    exit(0);   
 }  

注意:子进程进行 是从 父进程执行fork() 之后 开始执行

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{                          
    for(int i=0;i<2;i++)   
    {
        fork();
        printf("A\n");    //6个A  A  (A A  A  A)
    }
    return 0;
}

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
    for(int i=0;i<2;i++)
    {
        fork();
        printf("A");    //大于6个  8个--->缓冲区没有刷新  fork()会复制父进程的A
    }
    return 0;
}  A  AAA

进程状态:

  • 运行

  • 就绪

  • 阻塞

  • 僵死进程(defunc)(ps) exit(0)\return 0; exit_code=0--->会一直保存 父进程会可以查看子进程退出状态

    • wait(int *)--->查看后才释放 exit_code -128~127(8位)

    • wait() sys/wait.h 父进程调用wait()解决僵死进程

    • 子进程先于父进程结束,父进程没有获取子进程的退出码

    • 父进程先结束,子进程--->孤儿进程,系统给子进程分配父进程(早期----->pid=1 init)

    • ./fork &(后台运行) ---->ps

子进程 先于 父进程 结束时:

  1. 内核释放 子进程的大部分资源(内存、文件描述符等)。

  2. 但会保留子进程的进程控制块(PCB),里面包含了它的退出状态码

  3. 内核会向父进程发送一个 SIGCHLD 信号,通知它 “你的子进程结束了,快来收尸”。

  4. 如果父进程不调用 wait()waitpid() 来回收这个退出状态,子进程的 PCB 就会一直占用系统资源,这个进程就变成了僵尸进程

写时拷贝:提高fork()效率
#include <stdio.h>   
#include <stdlib.h>   
#include <unistd.h>   
#include <string.h>   
#include <assert.h>    
#include<wait.h>
int main(int argc, char* argv[],char* envp[])   
{      
    char * s = NULL;   
    int n = 0;        
    pid_t pid = fork();   
    assert( pid != -1 ); 
    if ( pid == 0 )   
     {   s = "child";   
         n = 3;   
     }   
    else   
     {   
         s = "parent";   
         n = 7; 
        int val=0;
        wait(&val);   //阻塞(子进程没有结束) wait(int *)
        if(WIFEXITED(val))//判断程序是否正常结束
        {
            printf("val=%d",WEXITSTATUS(val));//获取退出码的值
        }
        //printf("val=%d",val>>8);
     }      
    int i = 0;   
    for(; i < n; i++ )        
    {   
        printf("cur_pid=%d,p_pid=%d,s=%s\n",getpid(),getppid(),s); 
        sleep(1);  
     }   
    exit(0);   
 }  

库函数|系统调用

Linux操作文件的系统调用----->#include<fcntl.h>------>操作文件,需要用到(File Control Header)

fopen()库函数 调用open()

open/close read/write man 2(系统调用) write

Linux--->一切皆文件

  • O_RDONLY 只读打开

  • O_WRONLY 只写打开

  • O_RDWR 读写方式打开

  • O_CREAT 文件不存在则创建

  • O_APPEND 文件末尾追加

  • O_TRUNC 清空文件,重新写入

#include<stdio.h>
#include<fcntl.h>//操作文件,需要用到
int main()
{
    int fd=open("file.txt",O_CREAT|O_WRONLY,0600);// read:4  write:2 无:1
    if(fd==-1)
    {
        printf("open err\n");
        exit(1);
    }
    write(fd,"hello",5);
    close(fd);
    exit(0);
}
#include<stdio.h>
#include<fcntl.h>
int main()
{
    int fd=open("file.txt",O_RDONLY,0600);// read:4  write:2 无:1
    if(fd==-1)
    {
        printf("open err\n");
        exit(1);
    }
    char buff[128]={0};
    int n=read(fd,buff,127);
    printf("n=%d buff=%s\n",n,buff);
    close(fd);
    exit(0);
}
#include<stdio.h>
#include<fcntl.h>
int main()
{
    int fd=open("file.txt",O_RDONLY,0600);// read:4  write:2 无:1
    int fd1=open("file1.txt",O_CREAT|O_WRONLY,0600);
    if(fd==-1||fd1==-1)
    {
        printf("open err\n");
        exit(1);
    }
    int n;
    char buff[128]={0};
    while((n=read(fd,buff,128)>0)
    {
        write(fd1,buff,n);
    }
    //printf("n=%d buff=%s\n",n,buff);
    close(fd);
    exit(0);
}

复制图片

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
int main(int argc,char*argv[])
{
     if(argc!=3)
     {
     
         printf("arg err eg:./mycp a.png b.png ");
         exit(1);
     }
     char* s=argv[1];
     char* t=argv[2];
     int fd1=open(s,O_RDONLY);
     if(fd1==-1)
     {
         printf("filename:%s  open err\n",s);
         exit(1);
     }
     int fd2=open(t,O_WRONLY|O_CREAT,0600);
     if(fd2==-1)
     {
         printf("Create %s err\n",t);
         exit(1);
     }
     int num;
     char* wr[512];
     while((num=read(fd1,wr,512))>0)
     {
         write(fd2,wr,num);
     }
     exit(0);
}

父进程先打开的文件,fork后  子进程也可以访问 ,并且父子进程共享文件偏移量

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>

int main()
{
    int fd = open("file.txt",O_RDONLY);
    if( fd == -1 )
    {
        exit(1);
    }

    pid_t pid = fork();
    if( pid == -1 )
    {
        exit(1);
    }
    printf("fd=%d",fd);// 3
    if( pid == 0 )
    {
        char buff[32] = {0};
        read(fd,buff,1);
        printf("child read:%s\n",buff);
        sleep(1);
        read(fd,buff,1);
        printf("child read:%s\n",buff);
    }
    else
    {
        char buff[32] = {0};
        read(fd,buff,1);
        printf("parent read:%s\n",buff);
        sleep(1);
        read(fd,buff,1);
        printf("parent read:%s\n",buff);

    }

    close(fd);
    exit(0);
}
文件表(已打开的文件)

0 标准输入设备:FILE* stdin 1 (屏幕)标准输出设备: FILE* stdout

2 标准错误输出:FILE* stderr

(0,,1,2······)---->文字描述符

关闭一个文件后,再重新打开一个文件,会选择最小的序号

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>

int main()
{
    printf("A");//--->缓冲区  printf--->库函数
    write(1,"B",1);--->系统调用
    fork();
    exit(0);
}
//BAA

printf--->用户空间------->库函数

write()---->内核------>系统调用

系统调用和库函数的区别?
  • 系统调用

    • 操作系统内核提供的、用户程序请求内核服务的接口(内核态的入口),用户态内核态交互------>唯一合法途径

    • 需要从用户态--->内核态切换过程有性能消耗

  • 库函数:

    • 编程语言/库函数提供的封装函数,构建在系统调用或其他库函数之上,是面向开发者的高级接口

    • 是对系统调用的“封装/增强”(printf--->库函数,底层调用write,但增加了“格式化输出+缓冲区”)

    • 纯用户态(strlen/strcpy)、系统调用(可以通过优化减少切换次数,如printf的缓冲区)

系统调用的过程用户态→内核态的切换 + 内核服务执行 + 内核态→用户态的切回

  1. 用户态程序调用系统调用接口(如sys_read);

  2. 触发软中断(int 0x80 或 syscall 指令),CPU 切换到内核态

  3. 内核保存用户态上下文(寄存器、栈);

  4. 内核根据系统调用号执行对应内核函数

  5. 内核完成操作后,恢复用户态上下文,返回结果;

  6. CPU 切回用户态,程序继续执行。

信号、替换exec()系列

#define SIG

  • 发出信号 kill(pid_t pid,int sig);

  • 进程

  • 响应

    • 默认 0 SIG_DFL

    • 忽略 1 SIG_IGN (ignore)

    • 自定义 fun signal(); SIGCHLD----->17

函数指针====>sighandler_t

ctr+ \ ----->信号3 ctr+c --->信号2

void (*signal(int sig,void(*func)(int)))(int);
//分解
typedef void(*P)(int);//类型重命名   函数指针
P signal(int sign,P func);//信号
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void sig_fun(int sig)//sig信号的代号,内核传参
{
    printf("sig=%d\n",sig);
    signal(sig,SIG_DFL);//将SIGINT信号和对应方式调整为默认  第二次调用恢复默认
}
void main()
{
    signal(SIGINT,sig_fun);
    //signal(SIGINT,SIG_IGN);//忽略
    while(1)
    {
        printf("hello  pid=%d\n",getpid());
        sleep(1);
    }
}
发送信号

kill+pid (15)默认终止 mykill+pid sig 9-->强制杀死 2-->ctrl+c

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
// ./mykill  3345   2
int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        printf("argc err,eg:mykill 3345 2\n");
        exit(1);
    }
    int pid=atoi(argv[1]);
    int sig=atoi(argv[2]);
    if(kill(pid,sig)==-1)//发送信号
    {
        printf("kill err\n");
    }
    exit(0);
}
处理僵死进程
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<stdlib.h>
#include<sys/wait.h>
void sig_fun(int sig)
{
    printf("sig=%d",sig);
   int val=0;
    wait(&val);//不会阻塞,准确收到了子进程结束的信号
    //原来在父进程运行wait();-->会阻塞,要等子进程结束收到其退出码才继续执行
    printf("val=%d\n",val);
}
int main()
{
    int n=0;
    signal(SIGCHLD,sig_fun);//子进程结束——————>信号17
    //signal(SIGCHLD,SIGING);//直接忽略-->内核(仅在Linux)-->默认不需要子进程的退出码
    char* s=NULL;
    pid_t pid=fork();
    if(pid==-1)
    {
        exit(1);
    }
    if(pid==0)
    {
        n=3;
        s="child";
    }
    if(pid>0)
    {
        n=7;
        s="parent";
    }
    for(int i=0;i<n;i++)
    {
        printf("%s\n",s);
        sleep(1);
    }

    exit(0);
}
exec()进程替换

产生全新的进程:fork()+exec()

  • execl()

  • execlp()

  • execle()

  • execv

  • execvp

  • execvpe()---->系统调用 底层内核调用相同,只是函数封装,参数传递不同

which ps---->/usr/bin/ps

  • which:用于查找系统命令对应的可执行文件路径,它会在$PATH环境变量指定的目录中搜索目标命令的位置

  • pwd(print working directory):打印当前工作目录的绝对路径

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(int argc,char* argv[],char* envp[])
{
    printf("main pid=%d\n",getpid());
    execl("/usr/bin/ps","ps","-f",(char*) 0);//替换,用ps替换当前程序
    
    
    execlp("ps","ps","-f",(char*)0);//一旦含有p 均会在$PATH环境变量中查找 echo $PATH echo-->打印
    
    
    execle("/usr/bin/ps","ps","-f",(char*) 0",envp);//可以修改环境变量
    
    
    char* myargv[]={"ps","-f",0}
    execv("usr/bin/ps",myargv);
    
    execvp("ps",myargv);
    
    execvpe("/usr/bin/ps",myargv,envp);//系统调用
        
    //exec()无返回值,若替换成功,直接执行ps(已替换的进程),main程序将会消失      
    printf("exec err\n");
    exit(0);
}
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main(int argc,char* argv[],char* envp[])
{
    // 打印当前进程的PID,方便观察:替换后的ps进程和原main进程是同一个PID(进程替换不创建新进程)
    printf("main pid=%d\n",getpid());

    // 1. execl:l表示参数列表(list),需指定程序绝对路径,参数逐个传入,最后以(char*)0结尾
    execl("/usr/bin/ps","ps","-f",(char*) 0); 
    // 说明:一旦这行执行成功,后续所有代码都不会运行(进程被ps替换);只有失败才会执行下一行

    // 2. execlp:p表示搜索PATH环境变量(path),无需写绝对路径,参数列表形式
    execlp("ps","ps","-f",(char*)0); 
    // 对比execl:不用写/usr/bin/ps,系统会在$PATH里找ps命令的位置

    // 3. execle:e表示自定义环境变量(environment),参数列表+环境变量数组,原代码此处有语法错误(多了双引号)
    execle("/usr/bin/ps","ps","-f",(char*) 0, envp); // 修正:去掉(char*)0后的多余双引号

    // 4. execv:v表示参数数组(vector),需指定绝对路径,参数放在字符串数组中,数组以0结尾;原代码有2处错误:少分号、路径少/
    char* myargv[]={"ps","-f",0}; // 修正:补充分号
    execv("/usr/bin/ps",myargv);  // 修正:路径从usr/bin/ps改为/usr/bin/ps(少了根目录/)

    // 5. execvp:v(参数数组)+ p(搜索PATH),无需绝对路径,用参数数组传参
    execvp("ps",myargv);

    // 6. execvpe:v(参数数组)+ p(搜索PATH)+ e(自定义环境变量),系统调用级别的进程替换
    execvpe("/usr/bin/ps",myargv,envp);

    // 只有所有exec函数都替换失败时,才会执行到这里(打印错误提示)
    printf("exec err\n");
    exit(0);
}

exec 函数命名规则(帮你记忆):

后缀 含义
l list:参数以逐个列举的形式传入(如"ps","-f",(char*)0
v vector:参数放在字符串数组中传入(如myargv
p path:自动搜索 $PATH 环境变量,无需写程序绝对路径
e environment:自定义环境变量,需传入环境变量数组(如envp

参数要求

  • 无论l还是v,参数的第一个元素必须是程序名(如"ps"),后续是命令参数(如"-f"),最后必须以0(char*)0结尾(表示参数结束);

  • envp是 main 函数的第三个参数,保存了当前进程的环境变量(如 PATH、HOME 等),execle/execvpe可以通过它传递自定义环境变量。

bash--->命令解释器

mybash.c

while(1)

  • 键盘输入命令和参数 ls -l,cp a.c b.c

  • fork()

  • exec(ls)

#include<stdio.h>
#include<unistd.h>
#include<wait.h>
#include<string.h>
#include<stdlib.h>
#include<pwd.h>
//cp a.c b.c---->cp\0a.c\0b.c
char* get_cmd(char* buff,char** myargv)//char* myargv[]
{
    if(buff==NULL||myargv==NULL)
    {
        return NULL;
    }
    int i=0;
    char* s=strtok(buff," ");
    while(s!=NULL) 
    {
        myargv[i++]=s;
        s=strtok(NULL," ");//strtok会用 偷偷记录的位置(分隔符后的第一个字符) 作为起点
    }
    return myargv[0];
}
void print_info()
{
    char* user_str="$";
    int uid=getuid();//获取当前用户的id号
     if(uid==0)
     {
         user_str="#";
     }
    struct passwd* p=getppwuid(uid);
    if(p==NULL)
    {
        printf("mybush $");
        fflush(stdout);
        return;
    }
    //p->pw_name
    char host[128]={0};
    gethostname(host,128);//获取主机名
    
    char cur_dir[128]={0};//当前路径
    getcwd(cur_dir,128);
    printf("\033[1;32m%s@%s\033[0m:\033[1;34%s\033[0m%s",p->pw_name(),host,cur_dir,usr_str);
    fflush(stdout);
}
int main()
{
    while(1)
    {
        char buff[128]={0};
        /*printf("stu@localhost ~$");
        fflush(stdout);//强制刷新缓冲区
        */
        print_info();
        fgets(buff,128,stdin);//从键盘中读取数据 回车可以读到 ls--->"ls\n"
        //scanf();输入回车---->阻塞  无有效字符
        buff[strlen(buff)-1]='\0';//  0 --->"ls"
        char* myargv[10]={0};//主函数传参 内容
        char* cmd=get_cmd(buff,myargv);
        if(cmd==NULL)
        {
            continue;
        }
        
        if(strcmp(cmd,"exit")==0)//内置命令
        {
            break;
        }
        if(strcmp(cmd,"cd")==0)//内置命令
        {
            //改变当前路径
            if(myargv[1]!=NULL)
            {
                if(chdir(myargv[1])==-1)
                {
                    printf("cd err:%s not find",myargv[1]);
                }
            }
            continue;//只是路径发生变化
        }
        
        pid_t pid=fork();
        if(pid==-1)
        {
            continue;
        }
        if(pid==0)
        {
            execvp(cmd,myargv);//进程替换
            //exec()无返回值,替换成功直接执行子进程,否则继续执行main程序
            printf("execvp err\n");
            exit(1);
        }
        //父进程
        wait(NULL);//解决僵死进程---->需要等 子进程 先结束   父进程阻塞
                  //模拟前台
    }
}

内置命令(父进程):cd exit

普通命令(子进程):ls cp ps

/etc/passwd----->用户数据库

char* getcwd(char* buff,size_t size);//获取当前路径

实现ls
#include<stdio.h>
#include<unistd.h>
#include<wait.h>
#include<string.h>
#include<stdlib.h>
#include<dirent.h>
#include<sys/stat.h>
int main()
{
    char buff[256]={0};
    getcwd(buff,256);//获取当前位置
    DIR* pdir=opendir(buff);
    if(pdir==NULL)
    {
        printf("%s 目录流获取失败!!!\n",buff);
        exit(1);
    }
    struct dirent* s=NULL;
    while((s=readdir(pdir))!=NULL)
    {
        if(strncmp(s->d_name,".",1)==0)//以.开头的隐藏文件(文件打开状态)   ls -a
        {
            continue;
        }
        struct stat st;
        stat(s->d_name,&st);
        if(S_ISDIR(st.st_mode))//文件类型---->目录
        {
            printf("\033[1;34m%s\033[0m ",s->d_name);
        }
        else
        {
            if(st.st_mode&(S_IXUSR|S_IXGRP|S_IXOTH))//文件是否有执行权限
            {
                printf("\033[1;32m%s\033[0m ",s->d_name);
            }
            printf("%s ",s->d_name);
        }
        
    }
    printf("\n");
    closedir(pdir);
}

stat----->man 2 stat------->文件的属性信息 #include<sys/stat.h>

总结

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐