首先,推荐一本好书:«understanding unix-linux programming»,以下所有的灵感来自于这本书。
这里,重点记忆如下:
充分利用man的联机文档,技巧有
man -k key-word grep “xx”
这样可以有效抓取你想要的联机帮助。运气好的话可能会有code,要多多阅读、仔细阅读。
例如在这段代码中,首先 man who,结果可以知道,这个命令读取的是/var/run/utmp文件,接下来,使用
man -k utmp
可以看到一大堆的东西,学会甄别。
其实光知道概念意义不大,必须还会使用,这些命令的实现的价值也在于此。
int main(){
struct utmp current_record;
int utmpfd;
int reclen = sizeof(current_record);
if( ( utmpfd = open(UTMP_FILE,O_RDONLY)) == -1){
perror(UTMP_FILE); /* UTMP_FILE is in utmp.h*/
exit(1);
}
while(read(utmpfd, ¤t_record,reclen) == reclen)
show_info(¤t_record);
close(utmpfd);
return 0;
}
令人震撼的是read的实现,因为你不必实现知道要写入的结构、类型,只需要事先将结构定义好,然后按照read的使用要求就可以了。
#include <time.h>
time_t time(time_t *t);
功能: 以秒(seconds)为单位得到自从the Epoch的时间。
If t is non-null, the return value is also stored in the memory pointed to by t.
这里是有很多的坑要填的。相关的代码在
struct {
int32_t tv_sec; /* Seconds */
int32_t tv_usec; /* Microseconds */
} ut_tv; /* Time entry was made */
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
struct tm {
int tm_sec; /* seconds */
int tm_min; /* minutes */
int tm_hour; /* hours */
int tm_mday; /* day of the month */
int tm_mon; /* month */
int tm_year; /* year */
int tm_wday; /* day of the week */
int tm_yday; /* day in the year */
int tm_isdst; /* daylight saving time */
};
需要特别注意的是,年份是从1970年01月01日0:00:00开始计算的。
这就是在处理有关时间的问题时,经常打交道的三个结构体。
接下来是几个比较容易常用的有关时间的函数:
#include <time.h>
char *strptime(const char *s, const char *format,struct tm *tm);
功能: 将一个字符串的时间表达式转化进tm结构体。
这里要注意一下第二个参数,因为有些麻烦,所以最好是需要用的时候man一下。 这个函数在使用的时候,必须首先初始化一下tm。
size_t strftime(char *s, size_t max, const char *format,
const struct tm *tm);
功能: 格式化日期和时间(format data and time),其相关的信息来自于tm,第三个参 数和strptime函数类似,用的时候联机man 一下。
举个例子测试下:
/*
* $ ./a.out '%m'
* Result string is "11"
* $ ./a.out '%5m'
* Result string is "00011"
* $ ./a.out '%_5m'
* Result string is " 11"
*/
Here is the program source:
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
int
main(int argc, char *argv[])
{
char outstr[200];
time_t t;
struct tm *tmp;
t = time(NULL);
tmp = localtime(&t);
if (tmp == NULL) {
perror("localtime");
exit(EXIT_FAILURE);
}
if (strftime(outstr, sizeof(outstr), argv[1], tmp) == 0) {
fprintf(stderr, "strftime returned 0");
exit(EXIT_FAILURE);
}
printf("Result string is \"%s\"\n", outstr);
exit(EXIT_SUCCESS);
}
上面的注释将这段代码的功能描述的很清楚,也就是用’%Y’等格式化来当前时间关键 字段的输出。
将这两段程序综合起来看,就明白了
```c
#define _XOPEN_SOURCE
#include
int
main(void)
{
struct tm tm;
char buf[255];
memset(&tm, 0, sizeof(struct tm));
strptime("2016-07-06 18:31:01", "%Y-%m-%d %H:%M:%S", &tm);
strftime(buf, sizeof(buf), "%d %b %Y %H:%M", &tm);
puts(buf);
exit(EXIT_SUCCESS);
}
对大小是2500字节的文件进行copy操作,
如果缓冲区大小是100字节,需要25次read()和write();
如果缓冲区大小是1000字节,需要3次read() 和write()
缓冲区之所以对文件I/O性能有着显著的影响,这是因为每一次的read()、write()操作 必须经过系统调用,也就是内核访问。用户空间的程序每次必须经过内核才可以访问位 于 磁盘上的文件。
引入缓冲区,就是不必每次访问系统呼叫,只需一次调用,将内容放入缓冲区,每次用 户空间的程序可以访问缓冲区的内容,直至缓冲区的内容被消耗掉。
使用。磁盘是数据块集合,内核会对磁盘上的数据块作缓冲。内核将磁盘上的数据块复 制到内核缓冲区中,当一个用户空间中的进程要从磁盘上读取数据时,内核一般不直接 读磁盘,而是将内核缓冲区中的数据复制到进程的缓冲区中。
当进程所要求的数据块不在内核缓冲区中,内核会把相应的数据块加入到请求数据列表中,然后把该进程挂起,接着为其他进程服务。
一段时间后,内核把相应的数据块从磁盘读到内核缓冲区,然后再把数据复制到进程的缓冲区中,最后唤醒被挂起的进程。
大约不久前,我突然接触open的时候,也在想之前是不是接触过一个fopen函数,这两 个函数有什么区别,或者为什么有两个形式?看了上面的铺垫,想必你也有想法了,对 ,或者说,这是很重要的一方面。
open系列函数与fopen系列的区别可以简单概括为:
open系列函数 fopen系列函数 一般用于打开设备文件(少数情况) 一般用于打开普通文件(大多数情况) 利用文件描述符操纵文件(int) 利用文件指针操作文件 open返回一个文件描述符 fopen返回一个文件指针 POSIX系统调用 ANSI C库函数 低层次IO 高层次IO,对open的扩展和封装 只能在POSIX操作系统上 可移植到任何操作系统 非缓冲IO 缓冲IO 只能读取二进制或普通文 可以读取一个结构 可以指定要创建文件的访 限 不能指定要创建文件的访问权限
注: 文件指针的类型为 F *fp;
总结一下,就是带f前缀的是库函数,操作对象是file stream,不带f的原生态的 操作是unix下的系统调用。
用C标准I/O库函数要时刻注意I/O缓冲区和实际文件有可能不一致,在必要时需调用fflush(3)。
又因为c标准库函数是C标准的一部分,而Unbuffered I/O函数是UNIX标准的一部分,前者放在
其实还有一个函数也是比较重要的,尤其是确保内核内的数据写入磁盘,这个函数就是
#include<unistd.h>
void sync(void)
还有一个函数也是非常重要的
#include <unistd.h>
int fsync(int fd);
int fdatasync(int fd);
/**
bool isPerfectSquare(int num){
long long L = 1, R = (num >> 1) + 1;
while(L <= R){
long long middle = L + ((R - L)>>1);
long long value = middle * middle;
if(value == num)
return true;
else if(value > num)
R = middle - 1;
else
L = middle + 1;
}
return false;
}
各方面来说,目前的编程状态属于一种特别混乱。不过,自己还好涉猎面比较广泛, 竞赛算法类的编程,还可以拿的起来;linux系统级别的,需要自己钻时间挖掘知识点
这个系列肯定不完整,以后再加上其他语言版本的。
虽然已经这么的菜了,我就先不要发表自己的负面情绪了。
和前几年的状态一样,编程的思路还是不怎么正确,或者说c/c++的基础还是太low,不 过,现在到时有一点能稳定住。
其实这样也好,那边有linux kernel的大头维持着,这边你可以继续着算法方面的研修
这一次一定要坚持下去,不然真的要和计算机说再见了。
这道题目真的很简单,我也可以写到测试几个简单的数据,结果数据一大就错误了。
还有没有搞明白题目的含义,比如,测试数据
1001
我认为是有多少个26,就是多少个A,结果人家的答案是”ALM”,我的思路完全是错误的。
先把我的错误代码放上来,好好看看自己的弱点
char str[100];
char *convertToTitle(int n){
int len = n/26 + 1;
int i;
for (i = 0; i < len - 1 ;i++){
str[i] = 'A';
}
int last_num = n - n/26*26 -1;
str[i] = (char)(last_num + 'A');
str[len] = '\0';
return str;
}
关于对单个文件的操作,应该关注这篇文章
首先在联机帮助中寻找关键字direct来查找。
man -k direct grep read
这样就被指引到了readdir这个函数中,在这里面的说明文档,有以下内容
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result);
这个函数返回DIR *dir记录中的下一条目录项。
在linux上,dirent结构体的内容如下:
struct dirent {
ino_t d_ino; /* inode number */
off_t d_off; /* offset to the next dirent */
unsigned short d_reclen; /* length of this record */
unsigned char d_type; /* type of file; not supported
by all file system types */
char d_name[256]; /* filename */
};
其中的d_type的选项为:
DT_BLK This is a block device.
DT_CHR This is a character device.
DT_DIR This is a directory.
DT_FIFO This is a named pipe (FIFO).
DT_LNK This is a symbolic link.
DT_REG This is a regular file.
DT_SOCK This is a Unix domain socket.
DT_UNKNOWN The file type is unknown.
那么,与文件的操作相比较,还得有opendir,closedir等操作。
那么,ls的算法如下:
main()
opendir
while(readdir)
print d_name
closedir
那么,先看看v1.0是怎么实现的。
/*
* ls.c: implement of ls command
*/
#include<stdio.h>
#include<sys/types.h>
#include<dirent.h>
void do_ls(char *);
int main(int argc, char *argv[])
{
if( argc == 1)
do_ls(".");
else
while(--argc){
printf("%s:\n",*++argv);
do_ls(*argv);
}
}
void do_ls(char *dirname )
{
/*
* list files in directory called dirname
*/
DIR *dir_ptr; /* the directory */
struct dirent *direntp; /* each entry */
if ((dir_ptr = opendir(dirname)) == NULL)
fprintf(stderr, "ls1;cannot open %s\n",dirname);
else
{
while((direntp = readdir(dir_ptr)) != NULL)
printf("%s\n",direntp->d_name);
closedir(dir_ptr);
}
}
结果只是把所有的文件展示出来,但是还有很多的信息没有展示,比如:ls -l命令, 输出结果的排序等等。
想实现ls -l 的效果, 需要读取文件的信息状态,那么,这个信息由stst结构体提
供,相关的代码还是得看上面连接的部分
st_mode是一个16位的二进制数,图.
为了提取有效的数字,我们使用掩码技术。什么是掩码呢?就是好像带上面具将其他没 有的数据隐藏起来。
(1)掩码(masking) 会将不需要的字段置为0,需要的字段不发生变化。
(2)我们还知道整数在计算机中的表示也是二进制。这样一个普通的整数也就有了意义。
(3)与0做位与(&)操作可以将相应的bit为置为0,
(4) 使用8进制
介绍完了掩码,还必须介绍子域编码。
在 <bits/stat.h>中,有以下定义:
#define __S_IFMT 0170000 /* These bits determine file type. */
/* File types. */
#define __S_IFDIR 0040000 /* Directory. */
#define __S_IFCHR 0020000 /* Character device. */
#define __S_IFBLK 0060000 /* Block device. */
#define __S_IFREG 0100000 /* Regular file. */
#define __S_IFIFO 0010000 /* FIFO. */
if ((info.st_mode & 0170000) == 0040000)
printf("This is a directory");
然而在<sys/stat.h>中,又定义了上面的宏,有__X_IFXXX前面的__去掉了。
#include <bits/stat.h>
#if defined __USE_MISC || defined __USE_XOPEN
# define S_IFMT __S_IFMT
# define S_IFDIR __S_IFDIR
# define S_IFCHR __S_IFCHR
# define S_IFBLK __S_IFBLK
# define S_IFREG __S_IFREG
# ifdef __S_IFIFO
# define S_IFIFO __S_IFIFO
# endif
# ifdef __S_IFLNK
# define S_IFLNK __S_IFLNK
# endif
# if (defined __USE_MISC || defined __USE_UNIX98) \
&& defined __S_IFSOCK
# define S_IFSOCK __S_IFSOCK
# endif
#endif
更简单的方法是使用<sys/stat.h>中的macro,注意,这里版本的不同文件所处的位置也 不同的. 下面,具体的宏实现如下:
/* Test macros for file types. */
#define __S_ISTYPE(mode, mask) (((mode) & __S_IFMT) == (mask))
#define S_ISDIR(mode) __S_ISTYPE((mode), __S_IFDIR)
#define S_ISCHR(mode) __S_ISTYPE((mode), __S_IFCHR)
#define S_ISBLK(mode) __S_ISTYPE((mode), __S_IFBLK)
#define S_ISREG(mode) __S_ISTYPE((mode), __S_IFREG)
#ifdef __S_IFIFO
# define S_ISFIFO(mode) __S_ISTYPE((mode), __S_IFIFO)
#endif
#ifdef __S_IFLNK
# define S_ISLNK(mode) __S_ISTYPE((mode), __S_IFLNK)
#endif
#if defined __USE_MISC && !defined __S_IFLNK
# define S_ISLNK(mode) 0
#endif
这里,还可以定义如下宏:
#define S_ISFIFO(m) (((m)&(0170000)) == 0010000)
先提供struct passwd 的源码:
/* defined in pwd/pwd.h */
/* The passwd structure. */
struct passwd
{
char *pw_name; /* Username. */
char *pw_passwd; /* Password. */
__uid_t pw_uid; /* User ID. */
__gid_t pw_gid; /* Group ID. */
char *pw_gecos; /* Real name. */
char *pw_dir; /* Home directory. */
char *pw_shell; /* Shell program. */
};
补全命令:
大多数unix默认的shell是 ksh, ksh要用连续两次 ESC 来补全命令,HPUX默认的Shell是Posix shell
上条命令、下条命令: 同 vi 操作一模一样,先按 ESC键 进入命令模式
k 上一条 j 下一条 h 左移 l 右移 x 删除
开启AIX KSH 自动补全功能 AIX系统下默认的Shell是ksh,不同于Linux下的bash,ksh不直接支持dos-key的便捷操作,如果没有找到途径,每条命令都要重新敲入,实在是很麻烦。Ksh还不支持文件名自动补全,这也是另外一个遗憾。 实现方法: #set -o vi 上滚一条命令是ESC+k 下滚一条命令是ESC+j ESC + ESC -
自动补全是ESC+\
例如,想要自动补全,就先按ESC,再按\ 参考