linux系统编程(五)针对linux系统中文件的IO操作

简介: linux系统编程(五)针对linux系统中文件的IO操作

1.系统调用


什么是系统调用:

由操作系统实现并提供给外部应用程序的编程接口。(Application Programming Interface,API)。是应用程序同系统之间数据交互的桥梁。


C标准函数和系统函数调用关系。一个helloworld如何打印到屏幕。

1670988081867.jpg

2.C标准库文件IO函数


fopen、fclose、fseek、fgets、fputs、fread、fwrite......
  r 只读、 r+读写
w只写并截断为0、 w+读写并截断为0
a追加只写、 a+追加读写


3.open/close函数


3.1 函数原型


int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int close(int fd);


3.2 常用参数


O_RDONLY、O_WRONLY、O_RDWR    
O_APPEND、O_CREAT、O_EXCL、 O_TRUNC、 O_NONBLOCK
创建文件时,指定文件访问权限。权限同时受umask影响。结论为:
文件权限 = mode & ~umask
都变成二进制,掩码取反,然后与运算.
使用头文件:<fcntl.h>


3.3 open常见错误


1. 打开文件不存在 
2. 以写方式打开只读文件(打开文件没有对应权限)
3. 以只写方式打开目录


4.文件描述符


4.1 PCB进程控制块


PCB进程控制块:本质就是结构体,其中一个成员就是文件描述符表。文件描述符表中的每一个成员都是文件描述符。该表中能用的就是最小的。

0-STDIN_FILENO

1-STDOUT_FILENO

2-STDERR_FILENO

可使用命令locate sched.h查看位置:  
/usr/src/linux-headers-3.16.0-30/include/linux/sched.h  
struct task_struct
{
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
/*
表示进程的当前状态:
TASK_RUNNING:正在运行或在就绪队列run-queue中准备运行的进程,实际参与进程调度。
TASK_INTERRUPTIBLE:处于等待队列中的进程,待资源有效时唤醒,也可由其它进程通过信号(signal)或定时中断唤醒后进入就绪队列run-queue。
TASK_UNINTERRUPTIBLE:处于等待队列中的进程,待资源有效时唤醒,不可由其它进程通过信号(signal)或定时中断唤醒。
TASK_ZOMBIE:表示进程结束但尚未消亡的一种状态(僵死状态)。此时,进程已经结束运行且释放大部分资源,但尚未释放进程控制块。
TASK_STOPPED:进程被暂停,通过其它进程的信号才能唤醒。导致这种状态的原因有二,或者是对收到SIGSTOP、SIGSTP、SIGTTIN或SIGTTOU信号的反应,或者是受其它进程的ptrace系统调用的控制而暂时将CPU交给控制进程。
TASK_SWAPPING: 进程页面被交换出内存的进程。
*/
unsigned long flags;  //进程标志,与管理有关,在调用fork()时给出
int sigpending;     //进程上是否有待处理的信号
mm_segment_t addr_limit;   //进程地址空间,区分内核进程与普通进程在内存存放的位置不同
/*用户线程空间地址: 0..0xBFFFFFFF。
内核线程空间地址: 0..0xFFFFFFFF */
struct exec_domain *exec_domain;  //进程执行域
volatile long need_resched;     //调度标志,表示该进程是否需要重新调度,若非0,则当从内核态返回到用户态,会发生调度
unsigned long ptrace;
int lock_depth;  //锁深度
long counter;   //进程的基本时间片,在轮转法调度时表示进程当前还可运行多久,在进程开始运行是被赋为priority的值,以后每隔一个tick(时钟中断)递减1,减到0时引起新一轮调 度。重新调度将从run_queue队列选出counter值最大的就绪进程并给予CPU使用权,因此counter起到了进程的动态优先级的作用
long nice;     //静态优先级
unsigned long policy;  //进程的调度策略,有三种,实时进程:SCHED_FIFO,SCHED_RR,分时进程:SCHED_OTHER
//在Linux 中, 采用按需分页的策略解决进程的内存需求。task_struct的数据成员mm 指向关于存储管理的mm_struct结构。
struct mm_struct *mm;  //进程内存管理信息
int has_cpu, processor;
unsigned long cpus_allowed;
struct list_head run_list;  //指向运行队列的指针
unsigned long sleep_time;   //进程的睡眠时间
//用于将系统中所有的进程连成一个双向循环链表,其根是init_task
//在Linux 中所有进程(以PCB 的形式)组成一个双向链表,next_task和prev_task是链表的前后向指针
struct task_struct *next_task, *prev_task;
struct mm_struct *active_mm; //active_mm 指向活动地址空间。
struct linux_binfmt *binfmt;  //进程所运行的可执行文件的格式
int exit_code, exit_signal;
int pdeath_signal;    //父进程终止是向子进程发送的信号
unsigned long personality;
int dumpable:1;
int did_exec:1;
pid_t pid;   //进程标识符,用来代表一个进程
pid_t pgrp;  //进程组标识,表示进程所属的进程组
pid_t tty_old_pgrp;   //进程控制终端所在的组标识
pid_t session;      //进程的会话标识
pid_t tgid;
int leader;        //表示进程是否为会话主管
//指向最原始的进程任务指针,父进程任务指针,子进程任务指针,新兄弟进程任务指针,旧兄弟进程任务指针。
struct task_struct *p_opptr, *p_pptr, *p_cptr, *p_ysptr, *p_osptr;
struct list_head thread_group;   //线程链表
//用于将进程链入HASH表,系统进程除了链入双向链表外,还被加入到hash表中
struct task_struct *pidhash_next;
struct task_struct **pidhash_pprev;
wait_queue_head_t wait_chldexit;   //供wait4()使用
struct semaphore *vfork_sem;     //供vfork()使用
unsigned long rt_priority;      //实时优先级,用它计算实时进程调度时的weight值
//it_real_value,it_real_incr用于REAL定时器,单位为jiffies,系统根据it_real_value
//设置定时器的第一个终止时间.在定时器到期时,向进程发送SIGALRM信号,同时根据
//it_real_incr重置终止时间,it_prof_value,it_prof_incr用于Profile定时器,单位为jiffies。
//当进程运行时,不管在何种状态下,每个tick都使it_prof_value值减一,当减到0时,向进程发送信号SIGPROF,并根据it_prof_incr重置时间.
//it_virt_value,it_virt_value用于Virtual定时器,单位为jiffies。当进程运行时,不管在何种
//状态下,每个tick都使it_virt_value值减一当减到0时,向进程发送信号SIGVTALRM,根据it_virt_incr重置初值
unsigned long it_real_value, it_prof_value, it_virt_value;
unsigned long it_real_incr, it_prof_incr, it_virt_incr;
struct timer_list real_timer;  //指向实时定时器的指针
struct tms times;         //记录进程消耗的时间
unsigned long start_time;    //进程创建的时间
long per_cpu_utime[NR_CPUS], per_cpu_stime[NR_CPUS];//记录进程在每个CPU上所消耗的用户态时间和核心态时间
//内存缺页和交换信息:
//min_flt, maj_flt累计进程的次缺页数(Copyon Write页和匿名页)和主缺页数(从映射文件或交换
//设备读入的页面数);nswap记录进程累计换出的页面数,即写到交换设备上的页面数。
//cmin_flt, cmaj_flt,cnswap记录本进程为祖先的所有子孙进程的累计次缺页数,主缺页数和换出页面数。
//在父进程回收终止的子进程时,父进程会将子进程的这些信息累计到自己结构的这些域中
unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap;
int swappable:1;   //表示进程的虚拟地址空间是否允许换出
//进程认证信息
//uid,gid为运行该进程的用户的用户标识符和组标识符,通常是进程创建者的uid,gid,euid,egid为有效uid,gid
//fsuid,fsgid为文件系统uid,gid,这两个ID号通常与有效uid,gid相等,在检查对于文件系统的访问权限时使用他们。
//suid,sgid为备份uid,gid
uid_t uid,euid,suid,fsuid;
gid_t gid,egid,sgid,fsgid;
int ngroups;     //记录进程在多少个用户组中
gid_t groups[NGROUPS];  //记录进程所在的组
kernel_cap_t cap_effective, cap_inheritable, cap_permitted;//进程的权能,分别是有效位集合,继承位集合,允许位集合
int keep_capabilities:1;
struct user_struct *user;  //代表进程所属的用户
struct rlimit rlim[RLIM_NLIMITS];   //与进程相关的资源限制信息
unsigned short used_math;   //是否使用FPU
char comm[16];     //进程正在运行的可执行文件名
//文件系统信息
int link_count;
struct tty_struct *tty;  //进程所在的控制终端,如果不需要控制终端,则该指针为空
unsigned int locks; /* How many file locks are being held */
//进程间通信信息
struct sem_undo *semundo;  //进程在信号量上的所有undo操作
struct sem_queue *semsleeping;  //当进程因为信号量操作而挂起时,他在该队列中记录等待的操作
struct thread_struct thread;   //进程的CPU状态,切换时,要保存到停止进程的task_struct中
struct fs_struct *fs;     //文件系统信息,fs保存了进程本身与VFS(虚拟文件系统)的关系信息
struct files_struct *files; //打开文件信息,指向文件描述符号
//信号处理函数
spinlock_t sigmask_lock; /* Protects signal and blocked */
struct signal_struct *sig; //信号处理函数
sigset_t blocked;      //进程当前要阻塞的信号,每个信号对应一位
struct sigpending pending; //进程上是否有待处理的信号
unsigned long sas_ss_sp;
size_t sas_ss_size;
int (*notifier)(void *priv);
void *notifier_data;
sigset_t *notifier_mask;
/* Thread group tracking */
u32 parent_exec_id;
u32 self_exec_id;
spinlock_t alloc_lock; //用于申请空间时用的自旋锁。自旋锁的主要功能是临界区保护
};


4.2 文件描述图表


结构体PCB 的成员变量file_struct *file 指向文件描述符表。

从应用程序使用角度,该指针可理解记忆成一个字符指针数组,下标0/1/2/3/4…找到文件结构体。

本质是一个键值对0、1、2…都分别对应具体地址。但键值对使用的特性是自动映射,我们只操作键不直接使用值。

新打开文件返回文件描述符表中未使用的最小文件描述符。

STDIN_FILENO    0
STDOUT_FILENO   1
STDERR_FILENO 2

1670988197319.jpg


4.3 最大打开文件数


一个进程默认打开文件的个数1024。      
命令查看ulimit -a 查看open files 对应值。默认为1024    
 可以使用ulimit -n 4096 修改    
 当然也可以通过修改系统配置文件永久修改该值,但是不建议这样操作。
 cat /proc/sys/fs/file-max可以查看该电脑最大可以打开的文件个数。
受内存大小影响。


4.4 FIFE结构体


主要包含文件描述符、文件读写位置、IO缓冲区三部分内容。 
struct file {
  ...
  文件的偏移量;
  文件的访问权限;
  文件的打开标志;
  文件内核缓冲区的首地址;
  struct operations * f_op;
  ...  
    };      
查看方法:
    (1) /usr/src/linux-headers-3.16.0-30/include/linux/fs.h  
    (2) lxr:百度 lxr → lxr.oss.org.cn → 选择内核版本(如3.10) → 点击File Search进行搜索 
  → 关键字:“include/linux/fs.h” → Ctrl+F 查找 “struct file {” 
→ 得到文件内核中结构体定义
  → “struct file_operations”文件内容操作函数指针 
  → “struct inode_operations”文件属性操作函数指针

5. read/write 函数


ssize_t read(int fd, void *buf, size_t count); 
ssize_t write(int fd, const void *buf, size_t count); 
read与write函数原型类似。使用时需注意:read/write函数的第三个参数。


练习:编写程序实现简单的cp功能。


程序比较:如果一个只读一个字节实现文件拷贝,使用read、write效率高,还是使用对应的标库函数(fgetc、fputc)效率高呢?


5.1 strace命令


shell中使用strace命令跟踪程序执行,查看调用的系统函数。

1670988252946.jpg


5.2 缓冲区


read、write函数常常被称为Unbuffered I/O。指的是无用户及缓冲区。但不保证不使用内核缓冲区。


5.3 预读入缓输出

1670988272324.jpg


6.错误处理函数


错误号:errno
perror函数:    void perror(const char *s); 
strerror函数: char *strerror(int errnum); 
查看错误号:  
/usr/include/asm-generic/errno-base.h
     /usr/include/asm-generic/errno.h
#define EPERM  1  /* Operation not permitted */
#define ENOENT  2  /* No such file or directory */
#define ESRCH  3  /* No such process */
#define EINTR  4  /* Interrupted system call */
#define EIO   5  /* I/O error */
#define ENXIO  6  /* No such device or address */
#define E2BIG  7  /* Argument list too long */
#define ENOEXEC  8  /* Exec format error */
#define EBADF  9  /* Bad file number */
#define ECHILD  10  /* No child processes */
#define EAGAIN  11  /* Try again */
#define ENOMEM  12  /* Out of memory */
#define EACCES  13  /* Permission denied */
#define EFAULT  14  /* Bad address */
#define ENOTBLK  15  /* Block device required */
#define EBUSY  16  /* Device or resource busy */
#define EEXIST  17  /* File exists */
#define EXDEV  18  /* Cross-device link */
#define ENODEV  19  /* No such device */
#define ENOTDIR  20  /* Not a directory */
#define EISDIR  21  /* Is a directory */
#define EINVAL  22  /* Invalid argument */
#define ENFILE  23  /* File table overflow */
#define EMFILE  24  /* Too many open files */
#define ENOTTY  25  /* Not a typewriter */
#define ETXTBSY  26  /* Text file busy */
#define EFBIG  27 /* File too large */
#define ENOSPC  28  /* No space left on device */
#define ESPIPE  29  /* Illegal seek */
#define EROFS  30  /* Read-only file system */
#define EMLINK  31  /* Too many links */
#define EPIPE  32  /* Broken pipe */
#define EDOM  33  /* Math argument out of domain of func */
#define ERANGE  34  /* Math result not representable */


7.阻塞、非阻塞


读常规文件是不会阻塞的,不管读多少字节,read一定会在有限的时间内返回。从终端设备或网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞,如果网络上没有接收到数据包,调用read从网络读就会阻塞,至于会阻塞多长时间也是不确定的,如果一直没有数据到达就一直阻塞在那里。同样,写常规文件是不会阻塞的,而向终端设备或网络写则不一定。


现在明确一下阻塞(Block)这个概念。当进程调用一个阻塞的系统函数时,该进程被置于睡眠(Sleep)状态,这时内核调度其它进程运行,直到该进程等待的事件发生了(比如网络上接收到数据包,或者调用sleep指定的睡眠时间到了)它才有可能继续运行。与睡眠状态相对的是运行(Running)状态,在Linux内核中,处于运行状态的进程分为两种情况:


正在被调度执行。CPU处于该进程的上下文环境中,程序计数器(eip)里保存着该进程的指令地址,通用寄存器里保存着该进程运算过程的中间结果,正在执行该进程的指令,正在读写该进程的地址空间。


就绪状态。该进程不需要等待什么事件发生,随时都可以执行,但CPU暂时还在执行另一个进程,所以该进程在一个就绪队列中等待被内核调度。系统中可能同时有多个就绪的进程,那么该调度谁执行呢?内核的调度算法是基于优先级和时间片的,而且会根据每个进程的运行情况动态调整它的优先级和时间片,让每个进程都能比较公平地得到机会执行,同时要兼顾用户体验,不能让和用户交互的进程响应太慢。

阻塞读终端:    【block_readtty.c】
非阻塞读终端    【nonblock_readtty.c】
非阻塞读终端和等待超时  【nonblock_timeout.c】


注意,阻塞与非阻塞是对于文件而言的。而不是read、write等的属性。read终端,默认阻塞读。


总结read 函数返回值:  

1. 返回非零值:  实际read到的字节数
2. 返回-1:  1):errno != EAGAIN (或!= EWOULDBLOCK)  read出错
2):errno == EAGAIN (或== EWOULDBLOCK)  设置了非阻塞读,并且没有数据到达。
3. 返回0:读到文件末尾


附上测试代码:

阻塞方式打开设备文件:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
        char buf[10];
        int n;
        n=read(STDIN_FILENO,buf,10);
        if(n<0){
                perror("read STDIN_FILENO");
                exit(1);
        }
        write(STDOUT_FILENO,buf,n);
        return 0;
}


非阻塞方式打开设备文件:

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
        char buf[10];
        int fd,n;
        fd=open("/dev/tty",O_RDONLY|O_NONBLOCK);
        if(fd<0){
                perror("open /dev/tty");
                exit(1);
        }
tryagain:
        n = read(fd,buf,10);
        if(n<0){
                if(errno != EAGAIN ){ //if(errno!=EWOULDBLOCK)
                perror("read /dev/tty");
                exit(1);
                }else{
                write(STDOUT_FILENO,"try again\n",strlen("try again\n"));
                sleep(2);
                goto tryagain;
                }
        }
        write(STDOUT_FILENO,buf,n);
        close(fd);
        return 0;
}


非阻塞设置超时:

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MSG_TRY "try again\n"
#define MSG_TIMEOUT "time out\n"
int main(void)
{
        char buf[10];
        int fd,n,i;
        fd=open("/dev/tty",O_RDONLY|O_NONBLOCK);
        if(fd<0){
                perror("open /dev/tty");
                exit(1);
        }
        printf("open /dev/tty ok... %d\n",fd);
        for(i=0;i<5;i++){
                n = read(fd,buf,10);
                if(n<0){
                        if(errno != EAGAIN ){ //if(errno!=EWOULDBLOCK)
                        perror("read /dev/tty");
                        exit(1);
                        }else{
                        write(STDOUT_FILENO,MSG_TRY,strlen(MSG_TRY));
                        sleep(2);
                }
                }
        }
        if(i==5){
                write(STDOUT_FILENO,MSG_TIMEOUT,strlen(MSG_TIMEOUT));
        }else{
        write(STDOUT_FILENO,buf,n);
        }
        close(fd);
  return 0;
}


8.lseek函数


8.1 文件偏移


Linux中可使用系统函数lseek来修改文件偏移量(读写位置)


每个打开的文件都记录着当前读写位置,打开文件时读写位置是0,表示文件开头,通常读写多少个字节就会将读写位置往后移多少个字节。但是有一个例外,如果以O_APPEND方式打开,每次写操作都会在文件末尾追加数据,然后将读写位置移到新的文件末尾。lseek和标准I/O库的fseek函数类似,可以移动当前读写位置(或者叫偏移量)。

回忆fseek的作用及常用参数。 SEEK_SET、SEEK_CUR、SEEK_END
    int fseek(FILE *stream, long offset, int whence);  成功返回0;失败返回-1
      特别的:超出文件末尾位置返回0;往回超出文件头位置,返回-1
    off_t lseek(int fd, off_t offset, int whence); 失败返回-1;成功:返回的值是较文件起始位置向后的偏移量。
特别的:lseek允许超过文件结尾设置偏移量,文件会因此被拓展。
    注意文件“读”和“写”使用同一偏移位置。


【lseek.c】

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
int main(void){
        int fd,n;
        char msg[]="it's a test foe lseek\n";
        char ch;
        fd = open("lseek.txt",O_RDWR|O_CREAT,0644);
        if(fd<0)
        {
                perror("open lseek.txt error");
                exit(1);
        }
        write(fd,msg,strlen(msg));
        lseek(fd,0,SEEK_SET);
        while((n=read(fd,&ch,1))){
                if(n<0){
                        perror("read error");
                        exit(1);
                }
                write(STDOUT_FILENO,&ch,n);
        }
        close(fd);
        return 0;
}
~


8.2 lseek常用应用


1. 使用lseek拓展文件:write操作才能实质性的拓展文件。单lseek是不能进行拓展的。   
  一般:write(fd, "a", 1);        
od -tcx filename  查看文件的16进制表示形式
        od -tcd filename  查看文件的10进制表示形式
  使用truncate函数,直接拓展文件。
2. 通过lseek获取文件的大小:lseek(fd, 0, SEEK_END);  【lseek_test.c】
    【最后注意】:lseek函数返回的偏移量总是相对于文件头而言。


【lseek_test.c】

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
int main(int argc,char *argv[])
{
        int fd = open(argv[1],O_RDWR);
        if(fd == -1)
        {
                perror("open error");
                exit(1);
        }
        int lenth = lseek(fd,0,SEEK_END);
        printf("file size:%d\n",lenth);
        return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
int main(int argc,char *argv[])
{
        //open/lseek(fd,249,SEEK_END)/write(fd,"\0",1);
        int ret =truncate("lseek.txt",250);//in there ,it must own an knowned file.
        printf("ret = %d\n",ret);
        return 0;
}


9.fcntl函数


改变一个【已经打开】的文件的 访问控制属性。
    重点掌握两个参数的使用,
    F_GETFL 和 F_SETFL。
fcntl :
  int flgs =fcntl(fd,F_GETFL);
  获取文件状态:F_GETFL
  设置文件状态:F_SETFL


位图:

位图就是使用一个比特位来进行表示数据存在与否的信息。

这里面的flags就是位图

1670988471222.jpg

【fcntl.c】

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#define MSG_TRY "try again\n"
int main(void)
{
        char buf[10];
        int flags ,n;
        flags = fcntl(STDIN_FILENO,F_GETFL);
        if(flags == -1){
                perror("fcntl error");
                exir(1);
        }
        flags |= O_NONBLOCK;
        int ret = fcntl(STDIN_FILENO,F_SETFL,flags);
        if(ret == -1){
                perror("fcntl error");
                exit(1);
        }
tryagain:
        n=read(STDIN_FILENO,buf,10);
        if(n<0){
                if(errno != EAGAIN){
                        perror("read /dev/tty");
                        exit(1);
                }
        sleep(3);
        write(STDOUT_FILENO,MSG_TRY,strlen(MSG_TRY));
        goto tryagain;
        }       
        write(STDOUT_FILENO,buf,n);     
        return 0;
}


10.ioctl 函数


对设备的I/O通道进行管理,控制设备特性。(主要应用于设备驱动程序中)。
通常用来获取文件的【物理特性】(该特性,不同文件类型所含有的值各不相同)

11.传入输出参数


11.1传入参数


const 关键字修饰的 指针变量  在函数内部读操作。  char *strcpy(cnost char *src, char *dst);


11.2 传出参数


1. 指针做为函数参数 
2. 函数调用前,指针指向的空间可以无意义,调用后指针指向的空间有意义,且作为函数的返回值传出  
3. 在函数内部写操作。


11.3 传入传出参数

传入参数:
  1.指针作为函数参数
  2.通常const关键字修饰
  3.指针指向有效区域,在函数内部做读操作
传出参数:
  1.指针作为函数参数
  2.在函数调用之前,指针指向的空间可以无意义,但必须有效
  3.在函数内部,做写操作。
  4.函数调用结束后,充当函数返回值。
传入传出参数:
  1.指针作为函数参数
  2.在函数调用之前,指针有实际意义
  3.在函数内部,先做读操作,后做写操作
  4.函数调用结束后,充当函数返回值。


拓展阅读


关于虚拟4G内存的描述和解析: 一个进程用到的虚拟地址是由内存区域表来管理的,实际用不了4G。而用到的内存区域,会通过页表映射到物理内存。

所以每个进程都可以使用同样的虚拟内存地址而不冲突,因为它们的物理地址实际上是不同的。内核用的是3G以上的1G虚拟内存地址, 其中896M是直接映射到物理地址的,128M按需映射896M以上的所谓高位内存。各进程使用的是同一个内核。


首先要分清“可以寻址”和“实际使用”的区别。

其实我们讲的每个进程都有4G虚拟地址空间,讲的都是“可以寻址”4G,意思是虚拟地址的0-3G对于一个进程的用户态和内核态来说是可以访问的,而3-4G是只有进程的内核态可以访问的。并不是说这个进程会用满这些空间。

其次,所谓“独立拥有的虚拟地址”是指对于每一个进程,都可以访问自己的0-4G的虚拟地址。虚拟地址是“虚拟”的,需要转化为“真实”的物理地址。


好比你有你的地址簿,我有我的地址簿。你和我的地址簿都有1、2、3、4页,但是每页里面的实际内容是不一样的,我的地址簿第1页写着3你的地址簿第1页写着4,对于你、我自己来说都是用第1页(虚拟),实际上用的分别是第3、4页(物理),不冲突。

内核用的896M虚拟地址是直接映射的,意思是只要把虚拟地址减去一个偏移量(3G)就等于物理地址。同样,这里指的还是寻址,实际使用前还是要分配内存。而且896M只是个最大值。如果物理内存小,内核能使用(分配)的可用内存也小。

相关文章
|
2月前
|
Shell Linux
Linux shell编程学习笔记30:打造彩色的选项菜单
Linux shell编程学习笔记30:打造彩色的选项菜单
|
16天前
|
运维 监控 Shell
深入理解Linux系统下的Shell脚本编程
【10月更文挑战第24天】本文将深入浅出地介绍Linux系统中Shell脚本的基础知识和实用技巧,帮助读者从零开始学习编写Shell脚本。通过本文的学习,你将能够掌握Shell脚本的基本语法、变量使用、流程控制以及函数定义等核心概念,并学会如何将这些知识应用于实际问题解决中。文章还将展示几个实用的Shell脚本例子,以加深对知识点的理解和应用。无论你是运维人员还是软件开发者,这篇文章都将为你提供强大的Linux自动化工具。
|
1月前
|
搜索推荐 索引
【文件IO】实现:查找文件并删除、文件复制、递归遍历目录查找文件
【文件IO】实现:查找文件并删除、文件复制、递归遍历目录查找文件
34 2
|
1月前
|
编解码 Java 程序员
【文件IO】文件内容操作
【文件IO】文件内容操作
45 2
|
1月前
|
存储 Java API
【文件IO】文件系统操作
【文件IO】文件系统操作
40 1
|
2月前
|
Shell Linux
Linux shell编程学习笔记82:w命令——一览无余
Linux shell编程学习笔记82:w命令——一览无余
|
1月前
|
存储 Java 程序员
【Java】文件IO
【Java】文件IO
35 0
|
2月前
|
Shell Linux Python
python执行linux系统命令的几种方法(python3经典编程案例)
文章介绍了多种使用Python执行Linux系统命令的方法,包括使用os模块的不同函数以及subprocess模块来调用shell命令并处理其输出。
30 0
|
5月前
|
消息中间件 存储 缓存
【嵌入式软件工程师面经】Linux系统编程(线程进程)
【嵌入式软件工程师面经】Linux系统编程(线程进程)
125 1
|
6月前
|
Linux 调度 数据库
Linux下的系统编程——线程同步(十三)
Linux下的系统编程——线程同步(十三)
113 0
Linux下的系统编程——线程同步(十三)