一、系统组成
(1)系统:
1:软件(操作系统:内核,系统软件;应用程序)
2:硬件:CPU,内存,硬盘,键盘,鼠标,显卡 操作系统:内核或者内核和系统工具软件的组合
(2)启动和登录流程:上电,主板BIOS,boot,kernel,init,login,shell
配置文件:/ect/profile 系统启动时被执行~/.bashrc 用户登录时会调用
(3)文件:信息的集合,数据的集合,计算机处理的对象 IT行业处理信息:转换传输,存储
(4)错误处理:errno,strerror, perror
int main()
{
int fd = open("a.txt", O_RDWR);
if(fd < 0){
printf("errno is %d, error string = %s\n", errno, strerror(errno)); perror("open");
}
}
二、文件IO
(1)open/creat O_RDONLY、O_WRONLY、O_RDWR、O_CREAT(创建,如已存在,截断,需要增加文件权限)、O_TRUNC、O_APPEND、O_EXCL(与O_CREAT一起用,如存在则失败)
(2)close
(3)read/write
(4)文件指针,lseek
(5)文件读写效率,缓冲区(buf)设置1024~4096比较合适
(6)文件共享:两个进程同时打开同一个文件进行操作,会相互覆盖,有各自的文件指针
(7)dup:复制文件描述符,让文件描述符指向同一个文件结构
(8)原子操作:例:用APPEND写Log文件
(9)fcntl:这只文件描述符属性,文件状态属性,文件描述符复制,设置文件锁,设置文件通知等功能
例:添加O_APPEND属性
(10)文件映射 硬盘映射到进程的地址,效率高, 限制:文件长度必须大于等于映射长度,映射的offset必须是也得整数倍
页的尺寸获取方式:getconf -a | grep PAGE_SIZE
sysconf(_SC_PAGE_SIZE)
int main()
{
int fd = open("a.txt", O_RDWR);
void *ptr = mmap(NULL, 1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
*(char*)ptr = 'a';
munmap(ptr, 1024);
//close (fd);
}
(11)临时文件,mktemp
(12)缓存,O_SYNC(保证数据写入硬盘,IO效率低),sync/fsync/fsyncdata fwrite在用户空间和内核空间都有缓存,write只在内核空间有缓存
(13)标准输入0,标准输出1,标准错误2 STDIN_FLIENO,STDOUT_FILENO,STDERR_FILENO
(14)open返回值,可用最小文件描述符,文件描述符进程范围内唯一
(15)dup2(int oldfd, int newfd);
三、文件和目录:
Linux中的文件种类:普通文件、目录、符号链接,块设备,字符设备,管道,套接字
1、文件属性:stat
2、用户和组:实际账户和有限账户,文件权限(access测试文件某权限),umask 设置SUID,运行时使用拥有者的权限
3、文件长度,truncate,fseek,ftell
4、文件系统:inode(128-256Byte),数据块(1024-4096Byte) 文件、目录、引用计数器、stat(filename, struct stat *stat)/lstat
5、目录操作:opendir,closedir,readdir,rewinddir,telldir,seekdir
int rm(const char *path){
struct stat stat_buf;
int ret = stat(path, &stat_buf);
if(ret < 0){ perror("stat"); return -1; }
if(!S_IS_DIR(stat_buf.st_mode)){ unlink(path); return 0; }
char buf[1024]; DIR*dir = opendir(path);
if(dir == NULL) return -1;
struct dirent*entry = readdir(dir);
while(entry){
sprintf(buf, "%s/%s", path, entry->d_name);
if(entry->d_type == DT_REG || entry->d_type == DT_LNK) unlink(buf);
if(entry->d_type == DT_DIR){
if(strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { entry = readdir(dir); continue; }
rm(buf);
}
entry = readdir(dir);
}
close(dir);
rmdir(path);
return 0;
}
int main(){
long loc;
DIR*dir = opendir("testdir");
struct dirent*entry;
while(1){
loc = telldir(dir); entry = readdir(dir);
if(entry == NULL) break;
if(strcmp(entry->d_name, "a") == 0 break;
}
seekdir(dir, loc);
while(1){
entry = readdir(dir);
if(entry == NULL) break;
printf("loc is %d, entry->d_name=%s\n", (int)telldir(dir), entry->d_name);
}
seekdir(dir, loc);
}
四、进程环境:
1、内核和进程的关系:当系统启动时,内核代码被夹在到内存,初始化之后,启动第一个用户进程,然后内核的代码就等着用户进程来调度了。
2、进程是程序的实例;
3、PCB:进程运行时,内核为每个进程分配一个PCB(进程控制块,结构体:task_struct),描述进程信息;
4、虚拟地址空间
5、CPU:内核将进程PCB放入一个队列,总是让CPU服务队列中的第一个进程,以时间片为单位运行,排队运行,时间到了丢到队尾;
6、进程属性和状态:
PID:进程编号,不会重复,pid_t getpid()
PPID:父进程ID,pid_t getppid()
账户ID/组ID:getuid/geteuid(真实账户/有效账户),getgid/getegid
进程组ID:getpgrp、setpgid/会话组ID:getsid、setsid/控制终端:
环境变量:setenv、getenv,unsetenv
进程状态: 进程时间:times
当前工作目录:getcwd
动态库编译: gcc -fPIC -share xxx.c -o libxxx.so
程序运行时: export LD_LIBRARY_PATH=. 或将动态库放到/usr/lib
静态库打包:ar rcs libtest.a a.o b.o 链接:gcc a.c -llib -static
五、进程控制:
1、fork
2、wait/waitpid
3、僵尸进程:已经退出,但是父进程还没有调用wait回收的紫禁城 孤儿进程:父进程退出,子进程没有退出
4、exec:鸠占鹊巢 使用fork和exec执行一个新程序
5、不定参数:
void myprint(const char *filename, int line, const char *fmt, ...)
va_list ap; va_start(ap, fmt); va_arg(ap, const char */int...); va_end(ap);
6、 进程间关系:在Linux中,父子关系,组关系,session关系,进程和终端进程关系
六、信号:内核和进程通信的一种方式
1、信号类型:实时信号,非实时信号
2、信号的处理:通过signal函数,注册信号处理函数、如果没有注册信号处理函数,那么按照默认方式处理。
3、不可靠信号:如果进程收到一个信号来不及处理,这时候有收到一个同样的信号,MAME这两个信号回合秉承一个信号,因为进程保存该信号的值只有一位
4、终端系统调用:如果一个进程调用了某系统调用导致该进程处于挂起状态,而此时该进程收到一个信号,那么该系统调用被唤醒,通常该系统调用返回-1,错误码EINTR。
5、可重入问题:
6、忽略信号:signal(SIGPIPE, SIG_IGN);
7、屏蔽信号:sigprocmask
8、SIGCHLD:
9、sigaction用来注册信号处理函数,sigqueue用来发送信号 sigaction可以传递参数,可以获得发送信号的进程信息,可以设置SA_RESTART
七、线程:
线程也有PCB,他的PCB和进程的PCB结构完全一样,保存的虚拟地址空间和创建它的进程的虚拟地址空间完全一致
1、pthread_create(pthread_t *thread, const pthread_attr_t *atr/NULL,void *(*start_routine)(void *), void *arg);
2、线程标识:使用pthread_t标识线程,他是非负整数,由系统分配,保证在晋城范围内唯一
使用pthread_equal判断线程是否相等,使用pthread_self获取目前代码运行的线程
3、线程终止:例程返回,pthread_exit,pthread_cancel(异常退出)
线程里调用exit退出整个进程,主线程调用pthread_exit进程不会退出,但是主线程已经推出了,如果主线程main函数return,那么其他线程也结束了
4、线程回收:pthread_join回收线程PCB,等待编程结束
5、线程的同步: 锁(临界量):避免两个线程同时访问一个全局变量
锁会有效率低和思索问题,解决方式:
读写锁: 循环锁:
基本锁:类型:pthrea_mutex_t 一般在全局定义:pthread_mutex_t g_mutex; 初始化:ptread_mutex_init(&g_mutex,NULL); 加锁:pthread_mutex_lock(&g_mutex); 解锁:ptread_mutex_unlock(&g_mutex);
锁的粒度越大,效率越低
死锁:忘了解锁,重复加锁
循环锁:同一个线程进行多次加锁,不会阻塞:pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_Mutex_init(&mutex, &attr);
读共享写排他锁:pthread_rwlock_t mutex; pthread_rwlock_init(&mutex, NULL); pthread_rwlock_rdlock(&mutex); pthread_rwlock_wrlock(&mutex); pthread_rwlock_unlock(&mutex);
6、C++使用构造函数和析构函数自动加锁解锁
7、条件变量: pthread_cond_t g_cond; pthread_cond_init(&g_cond); pthread_cond_wait(&g_cond,&g_mutex); pthread_cond_signal(&g_cond);//唤醒 pthread_cond_broadcast(&g_cond);//全部唤醒
8、信号量: sem_t sem; sem_init(&sem, 0, 0 // 信号量初始值); sem_wait(&sem);//成功后sem值-1 sem_pose(&sem);//sem+=1
9、重入:如果函数操作了全局变量,这个函数就不是可重入函数了
10、分离的线程运行结束之后,他的PCB同时被释放了
11、线程私有数据:线程私有数据可以在该县城调用函数中访问,其他线程调用的函数中,不可访问。
pthread_key_t key; pthread_key_create(&key, 用来清理私有数据的函数指针); pthread_set_specific(key, data); void *data = pthread_get_specific(key);
12、线程取消:pthread_cancel pthread_setcancelstate(...);
八、守护进程:Daemon
规则:设置umask为0; 调用fork,并让父进程退出;
调用setuid创建新会话; 重新设置当前目录;关闭不需要的文件描述符;
重定向标准输入,输出,错误到dev/null
出错处理:调式信息:<syslog.h> void openlog(const char *ident, int option, int facility);
void syslog(int priority, const char *format, ...);
void closelog(void);
int setlogmask(int maskpri);
单例:使用文件锁来实现单例
惯例:单例文件路径在/var/run目录下,内容为该进程ID;配置文件在/etc目录下;启动脚本通常放在/etc/init.d目录下
九、高级IO
1、非阻塞IO O_NONblOCK标记打开文件,read不到会返回-1,errno被标记为EAGAIN; 如果open没有带上O_NONBLOCK,可以通过fcntl设置
2、记录锁:
int main(){
int fd = open("a.txt", ORDWR);
struct flock l;
l.l_type = F_WRLOCK;
l.l_whence = SEEK_SET;
l.l_start = 0;
l.l_len = 128;
int ret = fcntl(fd, F_SETLKW, &l);
l.l_type = F_UNLCK;
fcntl(fd, F_SETLKW, &l);
}
3、IO多路转接:
select:使用位域来表示描述符集合 让内核监听一个fd集合,当集合中有fd事件时,会返回有消息的fd子集
int main() {
int fd_mice = open("/dev/input/mice", O_RDONLY);
int fd_keyb = open("/dev/input/event1", O_RDONLY);
int readlen; char buf[1024];
while(1){
fd_set set;
FD_ZERO(&set);
FD_SET(fd_mice, &set);
FD_SET(fd_keyb, &set);
int nfds = fd_keyb+1;
struct timeval tv;
tv.tv_sec = 2;
tv.tv_userc = 0;
int ret = select(nfds, &set, NULL, NULL, &tv);
if(ret == -1){ if(errno == EINTR) continue; exit(1); }
if(ret == 0) printf("not input\n");
if(FD_ISSET(fd_mice, &set) readlen = read(fd_mice, buf, sizeof(buf));
if(FD_ISSET(fd_keyb, &set) readlen = read(fd_keyb, buf, sizeof(buf));
}
}
epoll:使用红黑树来保存文件集合
int main(){
int readlen; char buf[1024];
int fd_mice = open("/dev/input/mice", O_RDONLY);
if(fd_mice < 0) return 1;
int fd_keyb = open("/dev/input/event1",O_RDONLY);
if(fd_keyb < 0) return 1;
int epollfd = epoll_create(512);
struct epoll_event ev; ev.events = EPOLLIN; ev.data.fd = fd_mice;
int ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd_mice, &ev);
if(ret < 0) perror("epoll_ctl mice");
ev.events = EPOLLIN;
ev.data.fd = fd_keyb;
ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd_keyb, &ev);
if(ret < 0) perror("epoll_ctl keyb");
sturct epoll_event out_events[2];
while(1){
int ret = epoll_wait(epollfd, out_events, 2, 2000);
printf("epoll_wait return %d\n", ret);
if(ret < 0){ if(errno == EINTR) continue; else exit(1); }
if(ret == 0) printf("not input\n");
else{
int i;
for(i = 0; i < ret; ++i){
struct epoll_event* p = &out_events[i];
if(p->data.fd == fd_mice) printf("mouse event");
else if(p->data.fd == fd_keyb) printf("keyboard event");
}
}
}
}
4、存储映射IO