标签 c 下的文章

linux IO编程基本知识

一、标准C的I/O和

FILE结构体

typedef struct iobuf{
int cnt; /*剩余的字节数*/
char *ptr; /*下一个字符的位置*/
char *base; /*缓冲区的位置*/
int flag; /*文件访问模式*/
int fd; /*文件描述符*/
}FILE;

便准c的IO都是有缓存的,先把要操作的内容放到缓存中,最后在进行io的操作,譬如把某个字串读取到变量中,或者写数据到文件,可以查看 http://jinblog.com/archives/810.html 这篇文章的详细介绍

标准C的IO缓存类型

1、全缓存

要求填满整个缓存区后才进行I/O 系统调用操作。对于磁盘文件通常使用全缓存访问。

2、 行缓存

1)涉及一个终端时(例如标准输入和标准输出),使用行缓存。
2)行缓存满自动输出
3)碰到换行符自动输出

3、 无缓存

标准错误流stderr 通常是不带缓存区的,这使得错误信息能够尽快地显示出来。

下面的例子简单说明

#include <stdio.h>

int main()
{
        // 如下,执行程序是看不到输出内容的
        printf("Hello world!!!");
        // 加入 \n 是能看到内容的,因为标准输出是行级缓存的
        // printf("Hello world!!!\n");
        // 也可以使用fflush函数刷新输出缓冲,也能看到输出内容
        //fflush(stdout);
        while(1)
        {   
                sleep(1);
        }   
        return 0;
}

linux系统编程之基础必备(四):C 标准库IO缓冲区和内核缓冲区的区别

1.C标准库的I/O缓冲区
   UNIX的传统 是Everything is a file,键盘、显示器、串口、磁盘等设备在/dev 目录下都有一个特殊的设备文件与之对应,这些设备文件也可以像普通文件(保存在磁盘上的文件)一样打开、读、写和关闭,使用的函数接口是相同的。用户程序调用C标准I/O库函数读写普通文件或设备,而这些库函数要通过系统调用把读写请求传给内核 ,最终由内核驱动磁盘或设备完成I/O操作。C标准库为每个打开的文件分配一个I/O缓冲区以加速读写操作,通过文件的FILE 结构体可以找到这个缓冲区,用户调用读写函数大多数时候都在I/O缓冲区中读写,只有少数时候需要把读写请求传给内核。以fgetc / fputc 为例,当用户程序第一次调用fgetc 读一个字节时,fgetc 函数可能通过系统调用 进入内核读1K字节到I/O缓冲区中,然后返回I/O缓冲区中的第一个字节给用户,把读写位置指 向I/O缓冲区中的第二个字符,以后用户再调fgetc ,就直接从I/O缓冲区中读取,而不需要进内核 了,当用户把这1K字节都读完之后,再次调用fgetc 时,fgetc 函数会再次进入内核读1K字节 到I/O缓冲区中。在这个场景中用户程序、C标准库和内核之间的关系就像在“Memory Hierarchy”中 CPU、Cache和内存之间的关系一样,C标准库之所以会从内核预读一些数据放 在I/O缓冲区中,是希望用户程序随后要用到这些数据,C标准库的I/O缓冲区也在用户空间,直接 从用户空间读取数据比进内核读数据要快得多。另一方面,用户程序调用fputc 通常只是写到I/O缓 冲区中,这样fputc 函数可以很快地返回,如果I/O缓冲区写满了,fputc 就通过系统调用把I/O缓冲 区中的数据传给内核,内核最终把数据写回磁盘或设备。有时候用户程序希望把I/O缓冲区中的数据立刻 传给内核,让内核写回设备或磁盘,这称为Flush操作,对应的库函数是fflush,fclose函数在关闭文件 之前也会做Flush操作。

我们知道main 函数被启动代码这样调用:exit(main(argc, argv));。

main 函数return时启动代码会 调用exit ,exit 函数首先关闭所有尚未关闭的FILE *指针(关闭之前要做Flush操作),然后通 过_exit 系统调用进入内核退出当前进程.

C标准库的I/O缓冲区有三种类型:全缓冲、行缓冲和无缓冲。当用户程序调用库函数做写操作时, 不同类型的缓冲区具有不同特性。

  • 全缓冲

如果缓冲区写满了就写回内核。常规文件通常是全缓冲的。

  • 行缓冲

如果用户程序写的数据中有换行符就把这一行写回内核,或者如果缓冲区写满了就写回内 核。标准输入和标准输出对应终端设备时通常是行缓冲的。

  • 无缓冲

用户程序每次调库函数做写操作都要通过系统调用写回内核。标准错误输出通常是无缓冲的,这样用户程序产生的错误信息可以尽快输出到设备。

   除了写满缓冲区、写入换行符之外,行缓冲还有两种情况会自动做Flush操作。如果: 

用户程序调用库函数从无缓冲的文件中读取
或者从行缓冲的文件中读取,并且这次读操作会引发系统调用从内核读取数据

  如果用户程序不想完全依赖于自动的Flush操作,可以调fflush函数手动做Flush操作。 

#include <stdio.h>
int fflush(FILE *stream);
返回值:成功返回0,出错返回EOF并设置errno
fflush函数用于确保数据写回了内核,以免进程异常终止时丢失数据,如fflush(stdout); 作为一个特例,调 用fflush(NULL)可以对所有打开文件的I/O缓冲区做Flush操作。

2. 用户程序的缓冲区
   在函数栈上分配的如char buf[10];之类的缓冲区, strcpy(buf, str);  str 所指向的字符串有可能超过10个字符而导致写越界,这种写越界可能当时不出错, 而在函数返回时出现段错误,原因是写越界覆盖了保存在栈帧上的返回地址, 函数返回时跳转到非法地址,因而出错。像buf 这种由调用者分配并传给函数读或写的一段内存通 常称为缓冲区(Buffer),缓冲区写越界的错误称为缓冲区溢出(Buffer Overflow)。如果只是出 现段错误那还不算严重,更严重的是缓冲区溢出Bug经常被恶意用户利用,使函数返回时跳转到一 个事先设好的地址,执行事先设好的指令,如果设计得巧妙甚至可以启动一个Shell,然后随心所欲 执行任何命令,可想而知,如果一个用root 权限执行的程序存在这样的Bug,被攻陷了,后果将很 严重。

  下图以fgets / fputs 示意了I/O缓冲区的作用,使用fgets / fputs 函数时在用户程序中也需要分配缓冲 区(图中的buf1 和buf2 ),注意区分用户程序的缓冲区和C标准库的I/O缓冲区。
3.内核缓冲区
  • 终端缓冲

终端设备有输入和输出队列缓冲区,如下图所示

以输入队列为例,从键盘输入的字符经线路规程过滤后进入输入队列,用户程序以先进先出的顺序 从队列中读取字符,一般情况下,当输入队列满的时候再输入字符会丢失,同时系统会响铃警报。 终端可以配置成回显(Echo)模式,在这种模式下,输入队列中的每个字符既送给用户程序也送给 输出队列,因此我们在命令行键入字符时,该字符不仅可以被程序读取,我们也可以同时在屏幕上 看到该字符的回显。
注意上述情况是用户进程(shell进程也是)调用read/write等unbuffer I/O函数的情况,当调用printf/scanf(底层实现也是read/write)等C标准I/O库函数时,当用户程序调用scanf读取键盘输入时,开始输入的字符都存到C标准库的I/O缓冲区,直到我们遇到换行符(标准输入和标准输出都是行缓冲的)时,系统调用read将缓冲区的内容读到内核的终端输入队列;当调用printf打印一个字符串时,如果语句中带换行符,则立刻将放在I/O缓冲区的字符串调用write写到内核的输出队列,打印到屏幕上,如果printf语句没带换行符,则由上面的讨论可知,程序退出时会做fflush操作.

  • 虽然write 系统调用位于C标准库I/O缓冲区的底 层,被称为Unbuffered I/O函数,但在write 的底层也可以分配一个内核I/O缓冲区,所以write 也不一定是直接写到文件的,也 可能写到内核I/O缓冲区中,可以使用fsync函数同步至磁盘文件,至于究竟写到了文件中还是内核缓冲区中对于进程来说是没有差别 的,如果进程A和进程B打开同一文件,进程A写到内核I/O缓冲区中的数据从进程B也能读到,因为内核空间是进程共享的, 而c标准库的I/O缓冲区则不具有这一特性,因为进程的用户空间是完全独立的.

  • 为了减少读盘次数,内核缓存了目录的树状结构,称为dentry(directory entry(目录下项) cache

  • FIFO和UNIX Domain Socket这两种IPC机制都是利用文件系统中的特殊文件来标识的。FIFO文件在磁盘上没有数据块,仅用来标识内核中的一条通道,各进程可以打开这个文件进行read / write ,实际上是在读写内核通道(根本原因在于这个file 结构体所指向的read 、write 函数 和常规文件不一样),这样就实现了进程间通信。UNIX Domain Socket和FIFO的原理类似,也需 要一个特殊的socket文件来标识内核中的通道,文件类型s表示socket,这些文件在磁盘上也没有数据块。UNIX Domain Socket是目前最广泛使用 的IPC机制.如下图:

  • stack overflow 无穷递归或者定义的极大数组都可能导致操作系统为程序预留的栈空间耗尽 程序崩溃(段错误)

转载自 http://www.educity.cn/linux/1241891.html

Linux操作系统的构成

一、Linux操作系统的构成

####### 1、内核
1) 操作系统的核心,负责管理系统的进程、内存、设备驱动程序、文件和网络系统
2) 控制系统和硬件之间的相互通信
3) 决定着系统的性能和稳定性。
####### 2、Shell
####### 3、文件系统
####### 4、应用程序

二、Linux操作系统的基本概念

####### 1、文件和文件系统
1) 文件:数据或设备的一种逻辑组织
2) 文件系统:文件间关系管理的一种逻辑组织
####### 2、程序和进程
1) 程序: 计算机执行的指令集和
2) 进程: 程序的一个运行实例,操作系统资源分配的最小单位
####### 3、线程(轻量级进程)
程序运行的基本单位,一个进程内部可以有一或若干线程同时运行
####### 4、信号
Linux系统中进程 通信的一种技术,异步程序设计的基础

三、Linux操作系统的启动流程
  1. 芯片和部分外围电路的初始化
  2. 加载内核
[root@jin ~]# file /boot/vm*
/boot/vmlinuz-2.6.32-504.el6.x86_64:     Linux kernel x86 boot executable bzImage, version 2.6.32-504.el6.x86_64 (mockbuil, RO-rootFS, swap_dev 0x3, Normal VGA
  1. 加载最小文件系统
  2. 加载硬盘上的根文件系统
  3. 启动1号进程/sbin/init(0号为内核进程swapper进程),处理如下工作:
  4. 执行/etc/init.d目录中的所有脚本文件,启动某些系统的服务
  5. 执行/sbin/getty 初始化0、1和2(标准输入、标准输出和标准错误)
  6. 执行/bin/login启动用户登录程序
  7. 管理孤儿进程
四、BootLoader(uboot)
  • BootLoader就是在操作系统内核运行之前运行的一段小程序
  • 初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。
  • 严重依赖于硬件,在嵌入式开发中里建立一个通用的BootLoader几乎是不可能的,在Linux中称为grub。

Linux内存管理

一、Linux内存管理

程序所操作的都是虚拟内存,系统分配的都是虚拟内存空间
####### 1、进程隔离
保护独立的进程,防止互相干涉数据和存储空间
####### 2、自动分配和管理
动态地分配,分配对程序员是透明的
####### 3、支持模块化的程序设计
能够定义程序模块,并且动态地创建、销毁模块,改变模块大小
####### 4、保护和访问控制
允许一部分内存可以由各种用户以各种方式进行访问
####### 5、长期存储
关机后长时间保存信息

二、段页式内存管理

####### 1、进程在虚拟内存中分为代码段、数据段和堆栈段
数据段分为
+ 静态存储区,分为初始化区和为初始化区(bss),存放全局变量和局部变量
+ 常量区,常量
####### 2、进程在段中有许多固定大小的块组成,这些块成为页
####### 3、虚拟地址由段号、页号和页中偏移量构成
####### 4、虚地址和贮存中实地址(物理地址)的动态映射。
####### 5、缺页
1)消除了进程全部载入内存中
2)按需调页

系统调用

一、系统调用

所谓系统调用是指操作系统提供给用户程序的一组“特殊”接口,用户程序可以通过这组“特殊”接口来获得操作系统内核提供的特殊服务。

二、Linux进程的运行状态

####### 1、内核态
进程运行在内核空间
####### 2、用户态
进程运行在用户空间

三、IEEE POSIX标准
  1. 美国电气和电子工程师协会(IEEE)是一个国际性的电子技术与信息科学工程师的协会
  2. 在Linux中用户编程接口(API)遵循了在UNIX中最流行的应用编程界面标准—POSIX标准。这些系统调用编程接口主要通过C库(libc)实现的。

经常做一些linux下面的工作,想更深入的了解下linux下程序是怎么运行的,通过这个教程了解下

读取文件内容

//
//  main.c
//  读取文件的内容
//
//  Created by zhang on 16/4/10.
//  Copyright © 2016年 jin. All rights reserved.
//

#include <stdio.h>
/**
 输出文件包含的内容
 */
void test1()
{
    FILE *file = fopen("/Users/zhang/xcode/算法与数据结构/读取文件的内容/test.h", "r");
    //    file = fopen("test.h", "r");
    if (file != NULL) {
        char temp;
        while (fscanf(file, "%c",&temp) != EOF) {
            printf("%c",temp);
        }
        fclose(file);
    }
}

int main(int argc, const char * argv[]) {

    return 0;
}

九宫格字谜

//
//  main.c
//  算法与数据结构
//
//  Created by zhang on 16/4/10.
//  Copyright © 2016年 jin. All rights reserved.
//

#include <stdio.h>
#include <string.h>
void checkStr(const char str[]);
// 单词最大长度
#define WORDMAXLENGTH 10
// 九宫格基础数据
char data[][WORDMAXLENGTH] = {
    {'t','h','i','s'},
    {'w','a','t','s'},
    {'o','a','h','g'},
    {'f','g','d','t'},
};
// 字典,出现在这里面的词语就会
char dictionary[][WORDMAXLENGTH] =
{
    "this",
    "what",
    "fat",
    "good",
    "that"
};
/**
 查找九宫格中出现的单词
 */
void test1()
{
    // 获得最大的x轴和y轴坐标
    unsigned long maxX = strlen(data[0]);
    int maxY = sizeof(data) / sizeof(data[0]);
    // 循环遍历查找
    for (int x = 0; x < maxX; x++) {
        for (int y = 0; y < maxY; y++) {
            // 从左上角的字母开始
            char tempStringX[WORDMAXLENGTH] = {data[y][x]};
            char tempStringXY[WORDMAXLENGTH] = {data[y][x]};
            char tempStringXY2[WORDMAXLENGTH] = {data[y][x]};
            // 当前坐标,横向的所有组合
            for (int offsetX = 1; offsetX < (maxX - x); offsetX++) {
                tempStringX[offsetX] = data[y][x + offsetX];
                checkStr(tempStringX);
//                printf("%s\n",tempStringX);
            }
            // 当前坐标,纵向的所有组合
            char tempStringY[WORDMAXLENGTH] = {data[y][x]};
            for (int offsetY = 1; offsetY < (maxY - y); offsetY++) {
                tempStringY[offsetY] = data[y + offsetY][x];
                checkStr(tempStringY);
//                printf("%s\n",tempStringY);
            }
            // 当前坐标,斜对角的所有组合,从左往右
            for (int offsetXY = 1; offsetXY < (maxX - x); offsetXY++) {
                // 左上方向
                if (offsetXY < (maxY - y)) {
                    tempStringXY[offsetXY] = data[y + offsetXY][x + offsetXY];
                    checkStr(tempStringXY);
//                    printf("%s\n",tempStringXY);
                }
                // 当前坐标,斜对角的所有组合,右上方向
                if (offsetXY <= y) {
                    tempStringXY2[offsetXY] = data[y - offsetXY][x + offsetXY];
                    checkStr(tempStringXY2);
                    //                    printf("%s\n",tempStringXY);
                }
            }
        }
    }
    printf("%i\n",maxY);
}
/**
 翻转字符串
 */
void strReversal(char str[])
{
    char temp[WORDMAXLENGTH] = "";
    unsigned long length = strlen(str);
    for (int i = length; i >= 0; i--) {
//        temp[length - i] = str[i - 1];
        if ((length - i) >= (i - 1)) {
            break;
        }
        str[length - i] = str[i - 1] ^ str[length - i];
        str[i - 1] = str[i - 1] ^ str[length - i];
        str[length - i] = str[i - 1] ^ str[length - i];
    }
    char *tempPointer = temp;
//    return tempPointer;
}
void checkStr(const char str[])
{
    int length = sizeof(dictionary) / sizeof(dictionary[0]);
    for (int i = 0; i < length ; i++) {
        if (strcmp(str, dictionary[i]) == 0) {
            printf("找到一个:%s\n",str);
        }
        char tempStr[WORDMAXLENGTH];
        strcpy(tempStr, str);
        strReversal(tempStr);
        if (strcmp(tempStr, dictionary[i]) == 0) {
            printf("找到一个:%s\n",tempStr);
        }
    }
}
int main(int argc, const char * argv[]) {
    test1();
    return 0;
}